Error Codes
These are the error codes shared by every service library DAT officially supports.
Each code carries two values — impact and retry — and some also carry a suspect tag.
Impact — the damage to the service
This is the basis for alerting. It answers one question: "Is the service down right now?"
| Impact | Meaning | Example |
|---|---|---|
| Critical | The service or a specific feature stops. Issuance impossible, synchronization permanently failing, initialization failing | The issuing server holds no usable certificate at all |
| Partial | Some requests or cycles fail, but the service keeps running. It usually recovers on its own | One CMS cycle failed. The existing certificates keep working |
| No impact | The request is rejected. Nothing else happens | A tampered token arrived. Filtering it out is all that is needed |
No impact is not something to alert on. If every engineer on call has to look at a single piece of bad input, the alert stops meaning anything.
Suspect — investigate when it persists
A code tagged Suspect is part of normal operation when it happens once. A client can send a bad value at any time, and filtering it out is exactly the library's job.
But if such errors show up continuously, or in bursts from one source, it is one of two things.
- A configuration problem — a bad deployment, an old client still running, or certificates that no longer line up.
- An attack attempt — someone tampering with tokens or keys to get past verification, or probing for values that work.
So for these codes, track the count as a metric. Alert only when it crosses a threshold.
Retry
| Retry | Meaning |
|---|---|
| Transient | Retrying after a backoff will clear it |
| Permanent | Do not retry. Configuration or input has to be fixed |
| State | A signal, not an error |
Token
Problems with the received token string itself.
DAT_TOKEN_MALFORMED The dot-separated parts are not exactly five, or expire is not plain decimal, or cid is not plain hexadecimal, or plain/secure is not base64url, or a numeric field exceeded the integer range.
arrow_forwardReject the request
DAT_TOKEN_EXPIREDexpire <= now. The exact second counts as expired — expire == now is already treated as expired.
arrow_forwardPrompt for a token refresh
DAT_TOKEN_UNKNOWNA token error that falls into none of the categories above.
arrow_forwardCheck the logs
Expiry and a malformed token must stay separate
The responses are opposites — expiry is a normal end of life, so refreshing the token is enough, while a malformed token was never one we issued and has to be rejected.
Parsing settles the structure first, then looks at values. A string with too few parts, like "1.2.3", is not an expired token but not a token at all, so it is DAT_TOKEN_MALFORMED.
A signed expire field such as +100 is a format error too, not an expiry. Only plain ASCII digits are accepted.
Certificate
The format of the certificate string, and whether that certificate is usable right now.
DAT_CERT_MALFORMED The dot-separated parts are not exactly eight, or parsing cid/start/duration/ttl failed, or a key field is not base64url, or start + duration + ttl overflowed u64.
arrow_forwardRedeploy the certificate
DAT_CERT_EXPIREDstart + duration + ttl < now. Fully expired — neither issuance nor verification is possible.
arrow_forwardRenew the certificate
DAT_CERT_NOT_YET_ISSUABLEnow < start. The issuance window has not opened yet.
arrow_forwardWait
DAT_CERT_ISSUANCE_ENDEDnow > start + duration, but the ttl still has time left. Issuance is no longer possible; only verification is.
arrow_forwardDeploy a new certificate
DAT_CERT_VERIFY_ONLYA certificate that holds only the public key, without the signing private key. It can verify but cannot issue.
arrow_forwardCheck the deployment configuration
DAT_CERT_NOT_FOUND No certificate is held for the token's cid. Either the token is forged or the deployment is wrong.
arrow_forwardReject the request
DAT_CERT_NOT_SYNCED That cid has not arrived from the CMS yet. It appears briefly right after a new certificate is deployed.
arrow_forwardRetry after synchronization
DAT_CERT_DUPLICATE_CID The same cid appears more than once in the list being imported.
arrow_forwardCheck the server response
DAT_CERT_UNKNOWNA certificate error that falls into none of the categories above.
arrow_forwardCheck the logs
DAT_CERT_NOT_FOUND and DAT_CERT_NOT_SYNCED look the same from outside but call for different responses. The first is a cid we never issued, so waiting will not produce it; the second clears as soon as synchronization catches up.
A single DAT_CERT_NOT_FOUND just gets filtered out, but a sudden increase means the deployment has drifted or forged tokens are circulating.
Signature
DAT_SIG_MISMATCHSignature verification ended in a mismatch. The HMAC value differs, or ECDSA verify returned false.
arrow_forwardBlock the session, log to security
DAT_SIG_MALFORMED The signature part is empty, or is not base64url, or the ECDSA r‖s length does not match the curve, or the DER conversion failed.
arrow_forwardReject the request
DAT_SIG_KEY_MISSINGSigning was attempted with a verify-only key. At runtime, no private key is present.
arrow_forwardCheck the issuing server configuration
DAT_SIG_BACKENDThe signing or verification operation itself could not run. A wrong key type, a released handle, or an internal error in the crypto library.
arrow_forwardCheck the key type and library
DAT_SIG_UNKNOWNA signature error that falls into none of the categories above.
arrow_forwardCheck the logs
Do not conflate a mismatch with a backend failure
The two codes sit on opposite axes.
DAT_SIG_MISMATCH— an incoming signature simply did not match, so there is no service impact, but it is a suspect case if it persists.DAT_SIG_BACKEND— the verification operation itself could not run, so it is our problem, and it is not a suspect case.
Reporting a wrong key type or a library bug as a "signature mismatch" mixes our own broken code into the attack metrics. Conversely, classifying a real forgery as a backend error drops it out of the suspect metrics entirely.
Encryption
Problems encrypting and decrypting the secure payload.
DAT_CRYPTO_TAG_MISMATCHThe AES-GCM authentication tag does not match. Either secure was tampered with, or the certificate key is different.
arrow_forwardBlock the session, log to security
DAT_CRYPTO_DATA_INVALID The ciphertext is non-empty yet no longer than the IV (12 bytes), or the input exceeded an implementation limit such as INT_MAX.
arrow_forwardReject the request
DAT_CRYPTO_BACKENDThe encryption or decryption operation could not run. The platform does not support GCM, or context initialization failed.
arrow_forwardCheck platform support
DAT_CRYPTO_UNKNOWNAn encryption or decryption error that falls into none of the categories above.
arrow_forwardCheck the logs
An empty secure payload is not an error. Empty input yields empty output and produces no code at all.
On the path that skips signature verification, the GCM tag is the only integrity check. That is why DAT_CRYPTO_TAG_MISMATCH is not folded into the same code as other decryption failures.
Key
DAT_KEY_INVALID The key length does not match the declared algorithm (HMAC 32/48/64, AES 16/32), or the point is not on the curve, or d ∉ [1,n-1], or the format is not uncompressed (0x04), or the private and public keys are not a pair.
arrow_forwardReplace the key
DAT_KEY_VERIFY_ONLY_UNSUPPORTEDA verify-only export was requested for an HMAC-family algorithm.
arrow_forwardChange the algorithm
DAT_KEY_UNKNOWNA key error that falls into none of the categories above.
arrow_forwardCheck the logs
Three that look alike but are not:
| Code | Meaning |
|---|---|
DAT_KEY_VERIFY_ONLY_UNSUPPORTED | A structural limit of the algorithm. HMAC is symmetric, so it has no notion of a public key |
DAT_SIG_KEY_MISSING | A runtime state. This particular key does not currently hold a private key |
DAT_CERT_VERIFY_ONLY | A deployment shape. This certificate was deployed for verification only |
Manager
The state of the object that holds certificates and uses them to issue and verify.
DAT_MANAGER_NO_CERTIFICATENo certificate is held at all. Either import has not run yet, or the first CMS synchronization failed.
arrow_forwardCheck the CMS connection
DAT_MANAGER_NO_ISSUABLE_CERTIFICATECertificates exist, but none of them can be used for issuance right now. The cause is delivered along with it.
arrow_forwardDecide from the cause — see the table below
DAT_MANAGER_DISPOSEDAn already-disposed manager or certificate was used.
arrow_forwardFix the calling code
DAT_MANAGER_UNKNOWNA manager error that falls into none of the categories above.
arrow_forwardCheck the logs
The cause of DAT_MANAGER_NO_ISSUABLE_CERTIFICATE is one of four. Each one calls for something completely different.
| Cause | Meaning | Retry | Response |
|---|---|---|---|
DAT_CERT_NOT_YET_ISSUABLE | Before the issuance window starts | Transient | Waiting clears it |
DAT_CERT_ISSUANCE_ENDED | Issuance window closed; verification only | Permanent | A new certificate has to be deployed |
DAT_CERT_EXPIRED | Everything held has expired | Permanent | Certificates need renewing |
DAT_CERT_VERIFY_ONLY | Everything held is verify-only | Permanent | A deployment configuration mistake |
If an issuing server is configured to receive only verify-only certificates, DAT_CERT_VERIFY_ONLY is what comes out. Waiting will never clear it, so it is not a retry case.
Configuration
Problems with the values the caller passed in. Every CONFIG code is an error that requires a code fix; seeing one in production means the deployment is wrong.
DAT_CONFIG_ALG_UNSUPPORTED An unrecognized algorithm name. It has to match the wire notation exactly (ECDSA-P256, IV-AES256-GCM).
arrow_forwardCheck the algorithm name
DAT_CONFIG_ARGUMENT_INVALID A required argument is null, or out of range (a negative time value, interval <= 0), or of an unsupported type (passing a number or boolean as the payload in a dynamically typed language), or the body to be signed is empty.
arrow_forwardFix the calling code
DAT_CONFIG_URI_INVALIDThe CMS server URI is out of spec — unparseable, a scheme other than http/https, or carrying a path or query.
arrow_forwardFix the URI
DAT_CONFIG_UNKNOWNA configuration error that falls into none of the categories above.
arrow_forwardCheck the logs
Internal
Problems with the execution environment and the runtime.
DAT_INTERNAL_UNAVAILABLE The crypto backend or a runtime API is missing entirely. No crypto.subtle, a platform without AES-GCM, or a runtime version below the minimum.
arrow_forwardCheck the deployment and platform
DAT_INTERNAL_UNKNOWNMemory allocation failed, random generation failed, a lock could not be acquired, or a branch designed to be unreachable was reached.
arrow_forwardCheck the logs
DAT_INTERNAL_UNAVAILABLE is fixed by correcting the deployment environment, while DAT_INTERNAL_UNKNOWN is usually a runtime fault or a library bug.
CMS Sync
These codes never appear if CMS synchronization is not used.
DAT_CMS_UNREACHABLEDNS failure, connection refused, TLS failure, or a timeout. A timeout is not a separate code but is folded in here — the response is the same.
arrow_forwardRetry after a backoff
DAT_CMS_UNAUTHORIZEDThe server responded with 401. The token is missing or wrong.
arrow_forwardCheck the token configuration
DAT_CMS_FORBIDDENThe server responded with 403. The token is valid but has no permission for this endpoint.
arrow_forwardCheck the token tier
DAT_CMS_ENDPOINT_NOT_FOUNDThe server responded with 404. The URL is wrong.
arrow_forwardCheck the URL configuration
DAT_CMS_SERVER_ERRORThe server responded with 5xx.
arrow_forwardRetry after a backoff
DAT_CMS_HTTP_STATUSA non-2xx response not covered above.
arrow_forwardCheck the status code
DAT_CMS_MALFORMEDThe response has no version line, or the version line is not plain decimal, or it is out of range.
arrow_forwardCheck the server version
DAT_CMS_IMPORT_FAILED The response arrived, but the certificates could not be applied. The reason is carried in cause.
arrow_forwardCheck CERT_* / KEY_* in the cause
DAT_CMS_VERSION_RESETThe server returned a version older than ours. This is an instruction to resynchronize everything.
arrow_forwardHandled automatically
DAT_CMS_NOT_SYNCEDSynchronization has never succeeded even once.
arrow_forwardWait for the first synchronization
DAT_CMS_SYNC_IN_PROGRESSThe previous synchronization is still running, so this cycle was skipped. Not an error.
DAT_CMS_NOT_SUPPORTEDCMS support was not compiled in. The feature is disabled, or CURL is not bundled.
arrow_forwardCheck the build options
DAT_CMS_UNKNOWNA CMS error that falls into none of the categories above.
arrow_forwardCheck the logs
The codes that mark synchronization as a permanent failure (UNAUTHORIZED, FORBIDDEN, ENDPOINT_NOT_FOUND, MALFORMED, IMPORT_FAILED) are all critical. Retrying will not clear them while certificates keep expiring, so leaving them alone guarantees the service will stop.
UNREACHABLE and SERVER_ERROR, by contrast, are partial. The existing certificates keep working and the next cycle usually recovers — though repeated failure eventually escalates to critical. Alert on the number of consecutive failures.
Synchronization failures are not thrown
Even if the first synchronization fails, the manager is returned normally — synchronizing late is better than not starting at all. The failure is instead kept as queryable state.
| Client | How to read it |
|---|---|
| Rust | manager.last_error().await |
| Go | manager.LastError() |
| JavaScript | manager.lastError() |
| Python | manager.last_error() |
| Ruby | manager.last_error |
| Java/Kotlin | manager.lastError |
| C# | manager.LastError |
| C/C++ | dat_cms_manager_last_error(m) |
It holds DAT_CMS_NOT_SYNCED if synchronization has never succeeded, and is empty when everything is fine.
Server
Codes produced by the CMS server. Clients never produce these; they only receive them.
DAT_AUTH_UNAUTHORIZED The Authorization header is missing, or the token is not registered at any tier.
DAT_AUTH_FORBIDDENThe token is registered but is not of the tier this endpoint requires.
DAT_AUTH_DISABLEDNot a single token is configured, so authentication is disabled outright. Even the certificate issuance API is open without authentication. It is not returned in a response; it is only printed to the startup log.
arrow_forwardSet a token immediately
DAT_REQ_MALFORMEDA path or query parameter could not be parsed, or an argument is out of range (a negative delay, more than ten years, and so on).
DAT_REQ_ALG_UNSUPPORTEDThe algorithm name in the request path is unrecognized.
DAT_REQ_NOT_FOUNDNo such route, or the method does not match.
DAT_REQ_TOO_LARGEThe request body exceeded the size limit.
DAT_REQ_UNKNOWNA request error that falls into none of the categories above.
DAT_STORE_UNAVAILABLEThe database connection dropped, the connection pool is exhausted, locks are contended, or a timeout occurred. The only code that uses 503, which is how a client learns "this one clears if you wait."
arrow_forwardRetry after a backoff
DAT_STORE_UNKNOWNA read or write failed, a table is missing, the schema does not match, or a stored certificate row is corrupt.
arrow_forwardCheck the database state
Response envelope:
{
"code": "DAT_REQ_ALG_UNSUPPORTED",
"details": { "algorithm": "BOGUS-ALG" }
}For errors that arise while creating and handling certificates, the server uses the same common codes as above (DAT_CERT_*, DAT_KEY_*, DAT_CONFIG_*).
When a server code arrives
The client wraps the server code in its own CMS code and preserves the original in cause.
| Received | HTTP | Code the client produces |
|---|---|---|
DAT_AUTH_UNAUTHORIZED | 401 | DAT_CMS_UNAUTHORIZED |
DAT_AUTH_FORBIDDEN | 403 | DAT_CMS_FORBIDDEN |
DAT_REQ_NOT_FOUND | 404 | DAT_CMS_ENDPOINT_NOT_FOUND |
DAT_REQ_* (others) | 400·405·413 | DAT_CMS_HTTP_STATUS |
DAT_STORE_UNAVAILABLE | 503 | DAT_CMS_SERVER_ERROR |
DAT_STORE_UNKNOWN | 500 | DAT_CMS_SERVER_ERROR |
| (version rollback) | 200 | DAT_CMS_VERSION_RESET |
Finding it by symptom
| Symptom | Code |
|---|---|
| Works right after login, then gets rejected a while later | DAT_TOKEN_EXPIRED — the token reached the end of its life. Refreshing it is enough |
| Verification fails on one server only | DAT_CERT_NOT_SYNCED — that server has not received the new CID yet |
| The same token is rejected on every server | DAT_CERT_NOT_FOUND — a CID we never issued |
| The issuing server cannot create tokens | DAT_MANAGER_NO_ISSUABLE_CERTIFICATE + DAT_CERT_VERIFY_ONLY — it was deployed verify-only |
| Issuance fails only right after startup | DAT_MANAGER_NO_CERTIFICATE — before the first synchronization. It clears shortly |
| CMS synchronization keeps failing | DAT_CMS_UNAUTHORIZED — the token is wrong. Retrying will not clear it |
| No certificates arrive at all | DAT_CMS_ENDPOINT_NOT_FOUND — a typo in the URL |
| Fails on one platform only | DAT_INTERNAL_UNAVAILABLE — the crypto backend is missing |
| Verification failures suddenly spike | DAT_SIG_MISMATCH — one is harmless, but a burst means forgery attempts |
| Secure decryption suddenly fails | DAT_CRYPTO_TAG_MISMATCH — certificates drifted apart, or tampering |
| A warning in the CMS startup log | DAT_AUTH_DISABLED — authentication is off. The issuance API is wide open |
Appendix
Code syntax
DAT_<area>_<reason>- When the same reason arises in different areas, the reason name is the same.
DAT_TOKEN_MALFORMEDandDAT_CERT_MALFORMEDdiffer only in their subject; the meaning is identical. _UNKNOWNis reserved for the fallback in each area. It is never used to mean "unknown algorithm" or the like — that is_UNSUPPORTED.- The code string is a public contract. Messages may change freely, but codes do not.
| Area | Code prefix |
|---|---|
| Token | DAT_TOKEN_ |
| Certificate | DAT_CERT_ |
| Signature | DAT_SIG_ |
| Encryption | DAT_CRYPTO_ |
| Key | DAT_KEY_ |
| Manager | DAT_MANAGER_ |
| Configuration | DAT_CONFIG_ |
| Internal | DAT_INTERNAL_ |
| CMS Sync | DAT_CMS_ |
| Server | DAT_AUTH_ · DAT_REQ_ · DAT_STORE_ |
How each client exposes it
| Client | Error type | Code | Retry class | Security event |
|---|---|---|---|---|
| Rust | DatError enum | err.code() | err.retry() | err.security_event() |
| Go | *dat.Error | err.Code | dat.Retry(err) | dat.SecurityEvent(err) |
| JavaScript | DatError extends Error | e.code | e.retry | e.securityEvent |
| Python | DatError(ValueError, RuntimeError) | e.code | e.retry | e.security_event |
| Ruby | Saro::Dat::Error | e.code | e.retry | e.security_event? |
| Java/Kotlin | DatException | e.code | e.retry | e.securityEvent |
| C# | DatException | e.Code | e.Retry | e.SecurityEvent |
| C/C++ | dat_error_t | dat_error_code(e) | dat_error_retry(e) | dat_error_is_security_event(e) |
| CMS server | JSON envelope | code field | — | — |
security event returns true only for the two cases where forgery or tampering is certain (DAT_SIG_MISMATCH, DAT_CRYPTO_TAG_MISMATCH). The suspect tag in this document covers a wider range — tampered tokens, keys, and requests as well — and for now it is a documentation classification only, not exposed through the client API.
The impact grade is likewise a documentation classification, because the same code hits differently depending on where it arose — DAT_KEY_INVALID has no impact when it filters an incoming token, but if it comes up while reading certificates during CMS synchronization, the whole synchronization fails.
The underlying cause is never discarded. DAT_MANAGER_NO_ISSUABLE_CERTIFICATE and DAT_CMS_IMPORT_FAILED carry the reason through each language's exception chaining (cause / __cause__ / InnerException / Unwrap()).
C/C++ keeps the integer values too
The existing integer values of dat_error_t stay in place for ABI compatibility, but the string code is authoritative. The library no longer returns the old values, so a comparison like err == DAT_ERROR_INVALID_DAT will not match. Compare with dat_error_code(e) instead.
C has no exception chaining, so the cause is read separately via dat_manager_issuable_cause().