Errors.

Every failure has the same shape: a word your code matches on, a sentence for whoever is reading a terminal, and — where it helps — the numbers behind the refusal.

Working — v1 at api.ardaform.net

The envelope

Every error
{
  "error": {
    "code": "DUPLICATE_KEY",                         // match on this
    "message": "Records 0 and 1 both have the key 'A1'", // for people; reworded without notice
    "index": 1, "first": 0, "key": "A1"        // extras, per word (the table below)
  }
}
  • Match on code, never on message. A word never changes meaning inside v1; new words may be added (an endpoint may learn a new refusal), so treat an unknown word by its status.
  • The extras are the useful part: STORAGE_FULL says how full and by how much, BAD_PARAM names the parameter and what is accepted, OUT_OF_SCOPE names the area and the verb.
  • A 500 INTERNAL says nothing about our inside on purpose. Quote the time and the path to us.
  • The one answer that is not this shape: a 503 with an HTML body is the web server's own limit, before the API — see Limits.

Statuses

StatusMeans
200 201Done; 201 when something new was created. A delete reports what it removed rather than answering 204; only OPTIONS answers 204.
400Your request: a body, a field or a parameter.
401Your key: missing, unknown, revoked or expired.
403Allowed to ask, not to do: the key's scope, the package, a storage ceiling, the app's manifest.
404Not here: no such dataset, record, file, webhook, delivery, setting — or no such endpoint.
405The path exists; the method does not.
409The state is in the way: it exists already, it is switched off, its bytes were never uploaded.
410A file's bytes are missing on our side.
413The body is over 10 MB.
429The account's rate is spent: wait Retry-After seconds.
500 502Ours. 502 is the webhook delivery service not answering a Test.

Handling them

  • Retry 429 after Retry-After, and 500/502/503 with a backoff. Do not retry other 4xx — the same request gets the same answer.
  • Writes are safe to repeat: every record has a key, so an upsert of the same record twice is one record. A record without a key is refused (NO_KEY) — check a write's exceptions, which lists records refused while the rest stored.
  • 404 NO_DATASET right after a commit usually means the manifest has not been packed yet — the dataset does not exist until it is.
# -w prints the status after the body; -s keeps it quiet
curl -s -w '\n%{http_code}\n' "https://api.ardaform.net/v1/datasets/products" \
  -H "Authorization: Bearer $ARDA_KEY"
Using it the JavaScript and Python helpers ran live 2026-10-27: 404 NO_DATASET caught
// match on the word
try { await arda('/v1/datasets/products') }
catch (e) { if (e.code === 'NO_DATASET') { /* the manifest is not packed yet */ } else throw e }

Every word

Every error word the public API can answer. The list is generated from the API's own catalogue, and a test fails if the code answers a word that is not in it, or with a different status — so this table is the whole list. Each endpoint's own section lists the words it can answer.

StatusWordWhenAlso carries
400BAD_BASE64base64 is not valid base64 — it decodes to different bytes than it describes, or to nothing.
400BAD_FILE_NAMEThe file name is missing, holds a slash, or has no extension.name the name
mime the mime
accepted the extensions accepted
400BAD_FILE_TYPEThe extension is not on the allowlist: any image, plus documents, data, web, media and archive types.name the name
mime the mime
accepted the extensions accepted
400BAD_JSONThe body was sent as application/json and does not parse.
400BAD_KEY_FORMATA dataset key or webhook slug is not lower-case letters, digits, - and _, starting with a letter or digit, at most 80 — or is the reserved verify (webhooks).
400BAD_PARAMA query parameter the endpoint does not take, or a value it does not know. Refused rather than ignored, so a typo in a filter never returns everything.parameter the one refused
accepted what the endpoint takes (values for an unknown value)
400BAD_PARTITIONA partition value is an object or a boolean (records: in the dataset's partition_field; files: in partitions). A partition is a string or a number, or a list of them.index records: the record's position
field records: the partition field
400BAD_PATHA folder path holds . or .., a character a path cannot carry (\ ? * : | " < >), or is over 255 characters.
400BAD_RECORDAn element of the records array is not a JSON object.index its position, from 0
400BAD_REQUESTThe body is missing a required field, has one of the wrong type or length, or (on PUT /v1/content/…) arrived shorter or longer than its Content-Length. The message says which.
400BAD_RETENTIONretention_mode is not never, days or space, or its retention_value is missing or out of range (days ≥ 1; space 1–100).
400BAD_URLThe url is not http or https, is over 1024 characters, does not parse, or names localhost, a loopback, private or link-local address.
400CONFIRM_REQUIREDEmptying a whole dataset, removing a space (its devices to Unassigned, a private one's own data deleted), or turning a space's private off (its own data deleted) needs ?confirm=1.
400DUPLICATE_KEYTwo records in one call have the same key. Send it once, or give them different keys.index the second
first the first
key the key
400HASH_NOT_YOURSPOST /v1/files was sent a hash. The hash is computed from the bytes, by PUT /v1/content/{path} alone.upload the content path to PUT the bytes to
400INVALID_VALUEThe value does not fit the setting's type. The message gives the reason.key the setting key
400KEY_IMMUTABLEA PATCH sent a different dataset key. A key never changes — your devices address the dataset by it.
400NO_BODYA PUT to /v1/content/… with an empty body.
400NO_BRANCHThe project has no branch of that name — a space runs one of the project's branches.branch what was asked for
400NO_KEYA record write in which NO record has a key (id, key or record_key; empty or blank counts as none). Nothing is stored. When only some lack one, the rest store and each keyless one is listed in the answer's exceptions with this word instead. A record without a key cannot sync back to any device, so it is never stored. The same word and message a device push gets from connect.dataset the dataset key
refused how many records were sent, all of them keyless
400NOT_VERIFIABLEThe delivery's webhook has verify off (or no url), so the delivery carried no token.webhook the slug
400NOTHING_TO_DOA PATCH sent no field it can change (a dataset with none of its fields; a device policy with no key).
400RESERVED_KEYoutbox is the device runtime's own queue and cannot be a dataset key.
400SLUG_IMMUTABLEA PATCH sent a different slug. A slug never changes — your app addresses the webhook by it.
400TOO_MANYMore than 10,000 records in one call.limit 10000
sent how many were sent
400TYPE_MISMATCHThe mime says image and the extension does not.name the name
mime the mime
accepted the extensions accepted
401BAD_KEYNo key matches the one sent.
401KEY_EXPIREDThe key passed the end of its expiry day. Ask for a new one.expired_on the date it expired, YYYY-MM-DD
401KEY_REVOKEDThe key existed and was revoked in the portal. Ask why.
401NO_KEYNo Authorization header, or it is not Bearer af_….
403ACCOUNT_INACTIVEThe account is suspended or closed.
403API_CLOSEDThe app's manifest closes this dataset to the API: "api": "none". The fix is in the manifest, not on the key.dataset the dataset key
api the manifest's word
403API_READ_ONLYThe app's manifest makes this dataset read-only for the API: "api": "ro". Reads still work.dataset the dataset key
api the manifest's word
403DATASET_FULLThe write would take the dataset past its own storage_limit_bytes and its retention is never, so nothing is dropped to make room.limit_bytes the limit
used_bytes in use now
adding_bytes what this call adds
dataset the dataset key
403LIMIT_REACHEDThe package's ceiling on datasets (max_datasets), webhooks (webhook_endpoints), spaces beyond each project's Default (max_spaces) or devices (max_devices), each across the whole account, is reached.limit the ceiling
used how many exist
403NO_API_ACCESSThe account's package does not include API access.
403NO_SPACESThe account's plan has no spaces beyond each project's Default (max_spaces 0).
403NO_WEBHOOKSThe account's package does not include webhooks. Reads still work.
403NOT_WRITABLEThe app's manifest says "writable": {"api": false} for this setting — or the setting is a select whose choices are each device's own ("options": {"device": …}), chosen on each device — or, on PATCH /v1/devices/{uid}/policy, the manifest's screen.writable.api is false and orientation was sent. Read it; change it in the portal.key the setting key, or orientation
403OUT_OF_SCOPEThe key's scopes do not allow this area and verb (or this dataset, for records). A key edited before an area existed has no entry for it and is refused there until it is edited again in the portal.area the area: datasets, records, files, webhooks, project, settings or devices
action the verb: r, w, a or d
ref the dataset key, for records
403PROJECT_FULLThe write would take the project past the storage limit set on it in the portal — a fence on the project, not the account allowance.limit_bytes the limit
used_bytes in use now
adding_bytes what this call adds
project the project slug
403STORAGE_FULLThe write would take the account past its package's storage_mb. Code, datasets and files share it.limit_bytes the limit
used_bytes in use now
adding_bytes what this call adds
404NO_DATASETNo dataset with that key on the key's project.
404NO_DELIVERYNo such delivery: not this webhook's, never made, older than the log keeps (500 rows or seven days), or — for /verify — older than 24 hours.
404NO_DEVICENo device with that uid on the key's project. A uid is 24 hex characters; a device on another project answers the same, so nothing can be probed for.
404NO_FILENo file at that path on the key's project.
404NO_PENDING_DEVICENo device is showing that activation code right now: check the code on its screen, and that the device is switched on and online.
404NO_RECORDNo record with that key in the dataset.
404NO_ROUTENo endpoint at that path — a typo, or too many path segments.
404NO_SETTINGNo setting with that key on the key's project (keys are a-z, digits and _, starting with a letter).
404NO_SPACENo space with that id or slug in the key's project — in /v1/spaces/{space}, a space field or parameter, or the Arda-Space header. unassigned is no space (the devices in none). Another project's space answers the same, so nothing can be probed for.space what was asked for
404NO_VERSIONThe first path segment is not a version this API has. There is only v1.
404NO_WEBHOOKNo webhook with that slug on the key's project.
405BAD_METHODThe path exists and does not take that method. The message names the ones it does.
409DATASET_EXISTSA dataset with that key already exists on the project.
409NO_CONTENT_YETThe file is registered and its bytes were never uploaded.at the path
409NOT_A_PARENTThe key's project is a private space's store, which has no spaces of its own.
409NOT_ACTIVATEDThe device service refused the activation — a code tried too often, a device already registered. reason says which.reason the service's words
409SLUG_TAKENAnother space of this project has that slug — or it is one of the two reserved: default (the Default space) and unassigned (a device in none).slug the slug
409SPACE_DEFAULTThe Default space keeps its name and slug, uses the project's own data, and is never removed — every project has it. Its branch and max_devices change like any space's.
409SPACE_DEFINEDInside a private space (Arda-Space), a dataset cannot be made, changed or dropped: its datasets are the project's, mirrored. Do it without the header and every private space follows; its records are its own.
409SPACE_FULLThe space already holds its max_devices.limit its max_devices
409WEBHOOK_DISABLEDA Test on a webhook that is switched off.
409WEBHOOK_EXISTSA webhook with that slug already exists on the project.
410CONTENT_GONEThe file is registered and its bytes are not in storage. Ours, not yours — upload it again.at the path
hash the hash the row names
413TOO_LARGEThe body is over 10 MB (10,485,760 bytes) — every call, files included: a file can be 10 MB sent as it is, about 7.5 MB as base64. Nothing is stored.limit_bytes 10485760
429RATE_LIMITEDThe account's calls a minute are spent (api_per_min, bursts to api_burst). Wait Retry-After seconds. From POST /v1/webhooks/{slug}/test it means the account's webhook deliveries a minute instead.limit api_per_min
burst api_burst
retry_after seconds — also the Retry-After header
500INTERNALSomething failed on our side. The details are in our log, not in the answer; quote the time and the path.
502CONNECT_UNAVAILABLEThe device service did not answer — a webhook Test, an activation, or the wipes a space removal needs. Nothing was done; try again in a moment.

Examples

No such endpoint

curl "https://api.ardaform.net/v1/nothing-here" \
  -H "Authorization: Bearer $ARDA_KEY"
Answer · 404 Not Foundran live 2026-10-27 · test project
{
  "error": {
    "code": "NO_ROUTE",
    "message": "No endpoint at /v1/nothing-here."
  }
}

A method the path does not take

curl -X PUT "https://api.ardaform.net/v1/datasets" \
  -H "Authorization: Bearer $ARDA_KEY"
Answer · 405 Method Not Allowedran live 2026-10-27 · test project
{
  "error": {
    "code": "BAD_METHOD",
    "message": "Use GET or POST."
  }
}

No such version

curl "https://api.ardaform.net/v2/datasets" \
  -H "Authorization: Bearer $ARDA_KEY"
Answer · 404 Not Foundran live 2026-10-27 · test project
{
  "error": {
    "code": "NO_VERSION",
    "message": "There is no API version 'v2'; this API has v1."
  }
}

A body that does not parse

curl -X POST "https://api.ardaform.net/v1/datasets" \
  -H "Authorization: Bearer $ARDA_KEY" \
  -H "Content-Type: application/json" \
  --data-binary '{"name": "Products",'
Answer · 400 Bad Requestran live 2026-10-27 · test project
{
  "error": {
    "code": "BAD_JSON",
    "message": "The body is not valid JSON: Expected double-quoted property name in JSON at position 20 (line 1 column 21)"
  }
}