Skip to main content

Error Codes

Errors come in three shapes depending on where they originate. Handle all three — the API is served by several services, and which shape you get depends on the endpoint, not the status code.

HTTP Status Codes

CodeStatusDescription
200OKRequest successful
201CreatedResource created
202AcceptedAccepted for asynchronous processing
204No ContentSuccess with an empty body
400Bad RequestInvalid parameters, or a required workspace id is missing
401UnauthorizedInvalid or missing authentication
403ForbiddenInsufficient scope, or no access to that workspace
404Not FoundResource doesn't exist in this workspace
409ConflictDuplicate resource, or a locked accounting period
413Payload Too LargeFile upload over the size limit
422Unprocessable EntityValid request that cannot be applied — e.g. a price that won't convert
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer error
502Bad GatewayAn upstream exchange, chain or price provider failed

Shape 1: authentication and authorization

OAuth 2.0 format, with no success field. Emitted before your request reaches the endpoint.

{
"error": "insufficient_scope",
"error_description": "Missing: portfolios:read"
}
StatuserrorCause
401unauthorizedNo credentials, or an invalid/expired token
403insufficient_scopeValid credential, but it lacks the endpoint's scope
403forbiddenNot a member of the workspace, or the token is bound to a different one
400bad_requestWorkspace id required but not supplied
500server_errorFailure while authenticating

Solutions

  • unauthorized — send Authorization: Bearer … or x-api-key, not both. Check the token hasn't expired; access tokens last 24 hours on the OAuth flow.
  • insufficient_scopeerror_description names the missing scope. Request it at authorization time, or add it to your API key. Note contacts:*, invoices:* and swaps:* are not in the default client scope set.
  • forbidden — either you passed a workspace your credential can't reach, or you passed one at all on a workspace-bound token. See Workspaces.
  • bad_request — API keys and first-party sessions must pass ?wid=; OAuth tokens must not.

Shape 2: wrapped application errors

Used by integrations, providers, portfolios, credentials, syncs, CSV, user profile, assets, labels, spam and DeFi metadata.

{
"success": false,
"error": "Integration not found",
"code": "NOT_FOUND",
"details": {}
}
CodeStatusCause
VALIDATION_ERROR400Failed schema validation; details.issues lists each field
NOT_FOUND404No such resource in this workspace
CONFLICT409Would duplicate something unique — e.g. an integration alias
CREDENTIAL_VALIDATION_FAILED400The exchange or wallet rejected the credentials
SYNC_ERROR400Sync could not be started
FILE_REQUIRED400No file attached to a CSV upload
INVALID_FILE_TYPE400Not .csv, .xls or .xlsx
TOO_MANY_FILES400Over the per-request file limit
FILE_TOO_LARGE413Over the per-file size limit
RATE_LIMIT_EXCEEDED429Too many requests
EXTERNAL_SERVICE_ERROR502An upstream provider failed
INTERNAL_ERROR500Unexpected server error

Validation errors carry the offending fields:

{
"success": false,
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": {
"issues": [
{
"code": "invalid_type",
"expected": "number",
"received": "string",
"path": ["limit"],
"message": "Expected number, received string"
}
]
}
}

Solutions

  • EXTERNAL_SERVICE_ERROR and RATE_LIMIT_EXCEEDED are transient — retry with exponential backoff.
  • CONFLICT on integration creation usually means the alias is taken. Check first with GET /v1/integrations/alias-exists.
  • CREDENTIAL_VALIDATION_FAILED is the exchange's verdict, not ours. Validate up front with POST /v1/integrations/test-credentials rather than discovering it on the first sync.

Shape 3: nested errors (transactions and ledgers)

Transactions and ledgers nest the error and omit success.

{
"error": { "code": "PERIOD_LOCKED", "message": "Accounting period is locked" }
}
CodeStatusCause
BAD_REQUEST400Ineligible merge or split
NOT_FOUND404No such transaction or ledger in this workspace
PERIOD_LOCKED409Falls in a filed, locked accounting period
CURRENCY_CONVERSION_FAILED422A supplied price could not be converted
INTERNAL_ERROR500Unexpected server error

Both 409 and 422 reject the write whole — nothing is partially applied, so both are safe to retry once you've fixed the cause. For PERIOD_LOCKED, the period must be unlocked before the edit can land. For CURRENCY_CONVERSION_FAILED, check price.baseCurrency is a currency Kryptos can convert on that date.

Partial success is not an error

Several endpoints report per-item outcomes with a 2xx status. Treating them as wholly successful is a common source of silent data loss:

EndpointCheck
POST /v1/transactions/batchdata.errorCount
POST /v1/integrations/batch-deletedata.failed[]
POST /v1/integrations/resync-alldata.failed[]
POST /v1/csv/uploaddata.failures[]
A sync reaching partially_syncedrecordsFailed and message

Retry guidance

SituationRetry?
429, 502, 500Yes, with exponential backoff
422 CURRENCY_CONVERSION_FAILEDYes, after correcting the price
409 PERIOD_LOCKEDYes, after unlocking the period
401Only after refreshing the token — a bare retry will fail identically
400, 403, 404No — fix the request