DAT
Docs

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?"

ImpactMeaningExample
CriticalThe service or a specific feature stops. Issuance impossible, synchronization permanently failing, initialization failingThe issuing server holds no usable certificate at all
PartialSome requests or cycles fail, but the service keeps running. It usually recovers on its ownOne CMS cycle failed. The existing certificates keep working
No impactThe request is rejected. Nothing else happensA 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

RetryMeaning
TransientRetrying after a backoff will clear it
PermanentDo not retry. Configuration or input has to be fixed
StateA signal, not an error

Token

Problems with the received token string itself.

DAT_TOKEN_MALFORMEDNo impactPermanentshieldSuspect

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_EXPIREDNo impactPermanent

expire <= now. The exact second counts as expiredexpire == now is already treated as expired.

arrow_forwardPrompt for a token refresh

DAT_TOKEN_UNKNOWNPartialPermanent

A 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_MALFORMEDCriticalPermanent

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_EXPIREDCriticalPermanent

start + duration + ttl < now. Fully expired — neither issuance nor verification is possible.

arrow_forwardRenew the certificate

DAT_CERT_NOT_YET_ISSUABLECriticalTransient

now < start. The issuance window has not opened yet.

arrow_forwardWait

DAT_CERT_ISSUANCE_ENDEDCriticalPermanent

now > 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_ONLYCriticalPermanent

A 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_FOUNDNo impactPermanentshieldSuspect

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_SYNCEDPartialTransient

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_CIDCriticalPermanent

The same cid appears more than once in the list being imported.

arrow_forwardCheck the server response

DAT_CERT_UNKNOWNPartialPermanent

A 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_MISMATCHNo impactPermanentshieldSuspect

Signature verification ended in a mismatch. The HMAC value differs, or ECDSA verify returned false.

arrow_forwardBlock the session, log to security

DAT_SIG_MALFORMEDNo impactPermanentshieldSuspect

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_MISSINGCriticalPermanent

Signing was attempted with a verify-only key. At runtime, no private key is present.

arrow_forwardCheck the issuing server configuration

DAT_SIG_BACKENDPartialPermanent

The 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_UNKNOWNPartialPermanent

A 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_MISMATCHNo impactPermanentshieldSuspect

The 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_INVALIDNo impactPermanentshieldSuspect

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_BACKENDPartialPermanent

The encryption or decryption operation could not run. The platform does not support GCM, or context initialization failed.

arrow_forwardCheck platform support

DAT_CRYPTO_UNKNOWNPartialPermanent

An 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_INVALIDNo impactPermanentshieldSuspect

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_UNSUPPORTEDCriticalPermanent

A verify-only export was requested for an HMAC-family algorithm.

arrow_forwardChange the algorithm

DAT_KEY_UNKNOWNPartialPermanent

A key error that falls into none of the categories above.

arrow_forwardCheck the logs

Three that look alike but are not:

CodeMeaning
DAT_KEY_VERIFY_ONLY_UNSUPPORTEDA structural limit of the algorithm. HMAC is symmetric, so it has no notion of a public key
DAT_SIG_KEY_MISSINGA runtime state. This particular key does not currently hold a private key
DAT_CERT_VERIFY_ONLYA 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_CERTIFICATECriticalTransient

No 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_CERTIFICATECriticalPermanent

Certificates 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_DISPOSEDCriticalPermanent

An already-disposed manager or certificate was used.

arrow_forwardFix the calling code

DAT_MANAGER_UNKNOWNPartialPermanent

A 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.

CauseMeaningRetryResponse
DAT_CERT_NOT_YET_ISSUABLEBefore the issuance window startsTransientWaiting clears it
DAT_CERT_ISSUANCE_ENDEDIssuance window closed; verification onlyPermanentA new certificate has to be deployed
DAT_CERT_EXPIREDEverything held has expiredPermanentCertificates need renewing
DAT_CERT_VERIFY_ONLYEverything held is verify-onlyPermanentA 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_UNSUPPORTEDCriticalPermanent

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_INVALIDCriticalPermanent

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_INVALIDCriticalPermanent

The 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_UNKNOWNCriticalPermanent

A 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_UNAVAILABLECriticalPermanent

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_UNKNOWNCriticalPermanent

Memory 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_UNREACHABLEPartialTransient

DNS 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_UNAUTHORIZEDCriticalPermanent401

The server responded with 401. The token is missing or wrong.

arrow_forwardCheck the token configuration

DAT_CMS_FORBIDDENCriticalPermanent403

The server responded with 403. The token is valid but has no permission for this endpoint.

arrow_forwardCheck the token tier

DAT_CMS_ENDPOINT_NOT_FOUNDCriticalPermanent404

The server responded with 404. The URL is wrong.

arrow_forwardCheck the URL configuration

DAT_CMS_SERVER_ERRORPartialTransient5xx

The server responded with 5xx.

arrow_forwardRetry after a backoff

DAT_CMS_HTTP_STATUSCriticalPermanent

A non-2xx response not covered above.

arrow_forwardCheck the status code

DAT_CMS_MALFORMEDCriticalPermanent

The 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_FAILEDCriticalPermanent

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_RESETNo impactState200

The server returned a version older than ours. This is an instruction to resynchronize everything.

arrow_forwardHandled automatically

DAT_CMS_NOT_SYNCEDCriticalTransient

Synchronization has never succeeded even once.

arrow_forwardWait for the first synchronization

DAT_CMS_SYNC_IN_PROGRESSNo impactState

The previous synchronization is still running, so this cycle was skipped. Not an error.

DAT_CMS_NOT_SUPPORTEDCriticalPermanent

CMS support was not compiled in. The feature is disabled, or CURL is not bundled.

arrow_forwardCheck the build options

DAT_CMS_UNKNOWNPartialPermanent

A 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.

ClientHow to read it
Rustmanager.last_error().await
Gomanager.LastError()
JavaScriptmanager.lastError()
Pythonmanager.last_error()
Rubymanager.last_error
Java/Kotlinmanager.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_UNAUTHORIZEDNo impactPermanentshieldSuspect401

The Authorization header is missing, or the token is not registered at any tier.

DAT_AUTH_FORBIDDENNo impactPermanentshieldSuspect403

The token is registered but is not of the tier this endpoint requires.

DAT_AUTH_DISABLEDCriticalState

Not 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_MALFORMEDNo impactPermanentshieldSuspect400

A 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_UNSUPPORTEDNo impactPermanent400

The algorithm name in the request path is unrecognized.

DAT_REQ_NOT_FOUNDNo impactPermanentshieldSuspect404·405

No such route, or the method does not match.

DAT_REQ_TOO_LARGENo impactPermanentshieldSuspect413

The request body exceeded the size limit.

DAT_REQ_UNKNOWNNo impactPermanent400

A request error that falls into none of the categories above.

DAT_STORE_UNAVAILABLEPartialTransient503

The 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_UNKNOWNCriticalPermanent500

A 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:

json
{
  "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.

ReceivedHTTPCode the client produces
DAT_AUTH_UNAUTHORIZED401DAT_CMS_UNAUTHORIZED
DAT_AUTH_FORBIDDEN403DAT_CMS_FORBIDDEN
DAT_REQ_NOT_FOUND404DAT_CMS_ENDPOINT_NOT_FOUND
DAT_REQ_* (others)400·405·413DAT_CMS_HTTP_STATUS
DAT_STORE_UNAVAILABLE503DAT_CMS_SERVER_ERROR
DAT_STORE_UNKNOWN500DAT_CMS_SERVER_ERROR
(version rollback)200DAT_CMS_VERSION_RESET

Finding it by symptom

SymptomCode
Works right after login, then gets rejected a while laterDAT_TOKEN_EXPIRED — the token reached the end of its life. Refreshing it is enough
Verification fails on one server onlyDAT_CERT_NOT_SYNCED — that server has not received the new CID yet
The same token is rejected on every serverDAT_CERT_NOT_FOUND — a CID we never issued
The issuing server cannot create tokensDAT_MANAGER_NO_ISSUABLE_CERTIFICATE + DAT_CERT_VERIFY_ONLYit was deployed verify-only
Issuance fails only right after startupDAT_MANAGER_NO_CERTIFICATE — before the first synchronization. It clears shortly
CMS synchronization keeps failingDAT_CMS_UNAUTHORIZED — the token is wrong. Retrying will not clear it
No certificates arrive at allDAT_CMS_ENDPOINT_NOT_FOUND — a typo in the URL
Fails on one platform onlyDAT_INTERNAL_UNAVAILABLE — the crypto backend is missing
Verification failures suddenly spikeDAT_SIG_MISMATCH — one is harmless, but a burst means forgery attempts
Secure decryption suddenly failsDAT_CRYPTO_TAG_MISMATCH — certificates drifted apart, or tampering
A warning in the CMS startup logDAT_AUTH_DISABLEDauthentication 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_MALFORMED and DAT_CERT_MALFORMED differ only in their subject; the meaning is identical.
  • _UNKNOWN is 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.
AreaCode prefix
TokenDAT_TOKEN_
CertificateDAT_CERT_
SignatureDAT_SIG_
EncryptionDAT_CRYPTO_
KeyDAT_KEY_
ManagerDAT_MANAGER_
ConfigurationDAT_CONFIG_
InternalDAT_INTERNAL_
CMS SyncDAT_CMS_
ServerDAT_AUTH_ · DAT_REQ_ · DAT_STORE_

How each client exposes it

ClientError typeCodeRetry classSecurity event
RustDatError enumerr.code()err.retry()err.security_event()
Go*dat.Errorerr.Codedat.Retry(err)dat.SecurityEvent(err)
JavaScriptDatError extends Errore.codee.retrye.securityEvent
PythonDatError(ValueError, RuntimeError)e.codee.retrye.security_event
RubySaro::Dat::Errore.codee.retrye.security_event?
Java/KotlinDatExceptione.codee.retrye.securityEvent
C#DatExceptione.Codee.Retrye.SecurityEvent
C/C++dat_error_tdat_error_code(e)dat_error_retry(e)dat_error_is_security_event(e)
CMS serverJSON envelopecode 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().