Documentation / Developers

Errors

Every error code returned by the third-party authorization API, with its HTTP status, meaning and recommended handling, plus the Swift SDK error mapping.

Error format

Every application error is returned as JSON:

{ "error": "signature_invalid" }

Error codes are stable, machine-readable strings. Branch on the code, not on the HTTP status or any message text.

Important A few errors are not JSON. A body over 400,000 bytes returns 413. Endpoints that parse a fixed structure — POST /api/oauth/clients, POST /api/oauth/authorize, POST /api/oauth/token — return the framework's plain-text 400, 415 or 422 for malformed JSON, missing required fields or a missing Content-Type: application/json. On signed endpoints (/api/oauth/requests…, /api/oauth/revoke) any body parsing failure returns 400 invalid_input.

Signatures and tokens

Applies to every signed call (/api/oauth/account, /api/oauth/requests…, /api/oauth/vaults…).

HTTPCodeMeaningWhat to do
401invalid_tokenNo Authorization header, or the access token is unknownRefresh; if that fails, authorize again
401token_expiredThe access token has expired (3600-second lifetime)Refresh, then retry once
401grant_revokedThe grant was revoked by the user or by you, or its whole token family was revoked after refresh-token reuseStop retrying, wipe local credentials, ask the user to authorize again
401client_disabledThe platform operator disabled the clientStop calling; contact the platform
403client_disabledThe developer disabled the clientStop calling
401signature_requiredx-pm-timestamp, x-pm-nonce or x-pm-signature is missingCheck your signing code
401signature_invalidThe timestamp is not an integer, the nonce is not 8–128 characters, or the signature does not verifyCheck the canonical string: method, path without query, SHA-256 of the exact body bytes; do not modify the body after signing
401timestamp_out_of_windowThe timestamp is more than 300 seconds from server timeFix the device clock and retry
401nonce_replayedThe nonce was already used within this grantGenerate a fresh random nonce per request

Client registration

POST /api/oauth/clients, DELETE /api/oauth/clients/{id}.

HTTPCodeMeaningWhat to do
400invalid_inputname is empty or longer than 100 charactersFix the name
400invalid_client_typeclient_type is not public or confidentialFix the type
400invalid_redirect_urisNot 1–10 redirect URLs, or one violates the rules for the client typeSee redirect URL rules
400invalid_public_keyseal_public_key is not 32 bytes of base64urlFix the key
400invalid_platformsplatforms is not an array, has more than 10 entries, or contains invalid charactersUse lowercase letters, digits, _ and -
400invalid_brandbrand is not an object, or the color, icon URL or homepage is malformedUse #RRGGBB for the color and https for the icon
400mode_retiredA non-empty service_public_key was sent (mode 2 is retired)Remove the field
404client_not_foundThe client to disable does not exist, is not yours, or is already disabled—

GET /api/oauth/authorize/info, POST /api/oauth/authorize (called by the MimiKey app). When these occur in MimiKey, the integrator usually receives the callback error=invalid_request.

HTTPCodeMeaningWhat to do
404client_not_foundThe client_id does not existCheck the client_id
403client_disabledThe client was disabled by its developer or the platform—
400redirect_uri_mismatchThe redirect URL is not registeredMake it match a registered value exactly
400invalid_approval_keyThe approval public key is not 32 bytesCheck the approval_key deep-link parameter
400invalid_public_keyThe seal public key is not 32 bytesCheck the seal_key deep-link parameter
400seal_key_requiredA public client did not supply a seal keySupply seal_key
400code_challenge_requiredA public client did not supply a PKCE challengeSupply code_challenge
400invalid_code_challengeThe method is not S256, or the challenge is not 43–128 base64url charactersFix the PKCE values
400invalid_policypolicy is not ask or trust—
400invalid_selectionsNo vault selected, more than 32, duplicates, or an invalid mode—
400mode_retiredMode 2 was selectedUse mode 1 only
403vault_not_ownedA selected vault is not owned by the user—

Tokens

POST /api/oauth/token.

HTTPCodeMeaningWhat to do
400unsupported_grant_typegrant_type is not authorization_code or refresh_token—
400invalid_clientA public client sent a client_secretRemove client_secret
401invalid_client_secretThe client_id is unknown, or a confidential client's secret is wrongCheck the client_id and secret
403client_disabledThe client is disabled—
400invalid_codeThe code is unknown or already usedAuthorize again
400code_expiredThe code is older than 10 minutesAuthorize again
400code_client_mismatchThe code belongs to a different client—
400redirect_uri_mismatchredirect_uri differs from the one used at authorization—
400invalid_grantPKCE verification failed or code_verifier is missingSend the same verifier the challenge was made from; the code is now spent, so authorize again
401invalid_refresh_tokenThe refresh token is missing, unknown, or belongs to another clientAuthorize again
401refresh_expiredThe refresh token went unused for more than 30 daysAuthorize again
401refresh_token_reusedA rotated refresh token was presented again; every token of the grant has been revokedStop automatic retries immediately, wipe local credentials, ask the user to authorize again; check for concurrent refreshes or token leakage
401grant_revokedThe grant has been revokedAuthorize again

Revocation

POST /api/oauth/revoke. An unknown token returns { "ok": true } rather than an error.

HTTPCodeMeaningWhat to do
400invalid_inputThe body is not valid JSON or lacks fields—
400invalid_clientA public client sent a client_secretRemove client_secret and sign instead
401invalid_client_secretThe client_id is unknown, or a confidential client's secret is wrong—
401signature_required and relatedA public client revoking a live token sent no signature or a bad oneSign with that grant's approval private key

Requests

POST /api/oauth/requests, PUT /api/oauth/requests/{id}/patch, GET /api/oauth/requests/{id}, POST /api/oauth/requests/{id}/cancel.

HTTPCodeMeaningWhat to do
400invalid_inputThe body is not valid JSON, hint is not an object, or an update_item was created with patch_encFix the body
400invalid_opop is not a supported operation—
400item_id_requiredget_item or update_item lacks item_id—
400unsupported_kindThe create_item kind is not one of the six kinds—
400fields_requiredupdate_item lacks fields—
400invalid_fieldsfields is empty, has more than 32 entries, or contains an invalid nameUse lowercase letters, digits and _, starting with a letter
400invalid_patchpatch_enc is missing, larger than 64 KiB, or not a well-formed sealed boxCheck your sealing code
403vault_not_grantedThe target vault is not in the grant (for a cross-vault pick_item, the grant has no vaults)Use only vaults returned by the grant
403mode_not_grantedThe vault is in the grant but not in the mode this operation needs—
404request_not_foundThe request does not exist or does not belong to this grant—
409request_not_draftUploading a patch to a request that is no longer a draft: the 60-second draft expired, the patch was already uploaded, or it is not an update_itemCreate a new update_item and upload the patch within 60 seconds
409request_not_pendingCancelling a request that has already finishedGET it to read the final state
429too_many_requestsThe grant already has 100 unfinished requests (drafts included)Cancel requests you no longer need, or wait for them to expire

Device and user side

Used by the MimiKey apps; integrators normally never see these.

HTTPCodeMeaning
401lockedThe account session is missing or expired
401device_revoked_or_rotatedThe session's device was revoked or re-keyed
403untrusted_originThe request's Origin does not match the service
404grant_not_foundThe grant does not exist or is not the current user's
409request_not_pendingThe request already finished, or approve was sent for a pick, create or update request
409request_not_approvedA read result was uploaded before the request was approved

Operator side

HTTPCodeMeaning
401admin_lockedMissing or invalid administrator session
400reason_requiredNo reason (1–500 characters) given for disable or enable
404client_not_foundThe client does not exist

Server errors

HTTPCodeMeaningWhat to do
500database_errorServer storage is temporarily unavailableRetry with exponential backoff

Retired codes

These codes belong to mode 2. In v3, mode_retired is checked first, so they no longer occur in practice: service_key_missing, wrap_missing.

Callback errors

When MimiKey returns to your redirect URL it may include an error parameter:

ErrorWhenWhat to do
access_deniedThe user tapped Deny on the consent cardRespect the decision
not_signed_inMimiKey has no signed-in accountAsk the user to sign in to MimiKey first
invalid_requestMalformed deep link, unknown or disabled client, or unregistered redirect URLCheck the client_id and redirect URL
request_not_foundThe request does not exist or belongs to a different MimiKey accountCheck that the user is signed in to the same account they authorized with

Request callbacks also carry status: completed, denied, cancelled or expired. These are request outcomes, not errors; see the callback summary.

SDK error mapping

The Swift SDK maps server error codes, request states and callbacks onto MimiKeyError:

MimiKeyErrorSource
.notInstalled(downloadURL:)mimikey:// cannot be opened (not installed, or mimikey missing from LSApplicationQueriesSchemes on iOS)
.notAuthorizedNo grant in the device keychain
.cancelledcancelPending() was called or the Task was cancelled; the request state is cancelled
.deniedCallback error=access_denied; the request state is denied
.conflictThe request state is denied with deny_reason conflict
.expiredThe request state is expired; no result 15 seconds past expires_at; authorization waited longer than authorizeTimeout; 150 consecutive polling network failures
.notSignedInCallback error=not_signed_in
.revokedgrant_revoked, client_disabled, refresh_token_reused, invalid_refresh_token, refresh_expired, invalid_client, invalid_client_secret, or invalid_token persisting after a refresh. The SDK wipes local credentials
.network(Error)A URLSession failure (polling failures while waiting are retried automatically)
.protocol(String)Any other server code (such as vault_not_granted or invalid_grant), any other callback error (such as invalid_request or request_not_found), and the SDK-internal codes below

token_expired and invalid_token are never thrown directly: the SDK refreshes and retries once first.

SDK-internal .protocol codes:

CodeMeaning
nothing_pendingretryOpen() was called with no flow waiting
invalid_callbackThe authorization callback carried no code
no_granted_vaultcreateItem had no vault and the grant has none
empty_patchupdateItem was called with an empty patch
updated_item_missingAn update completed but the server returned no updated_item
result_missingA request completed without result_enc
result_open_failedThe result could not be opened with the local seal private key (key or AAD mismatch)
result_malformedThe result plaintext did not have the expected structure
malformed_responseThe server response was not the expected JSON
http_<status>A non-2xx response without a JSON error body

Last updated: 2026-10-23