Skip to content
CredenShare
API Reference

Errors and Rate Limits

The error envelope, every error code you can hit, request-rate limits, and what an authentication failure actually looks like.

Everything the API refuses, and how to tell the refusals apart.

The error envelope

Every error produced by the API itself uses one flat envelope:

{
  "success": false,
  "message": "Validation failed",
  "error_code": 19,
  "additional_data": {
    "encryption_type": "<why this field was rejected>"
  }
}
FieldNotes
successAlways false on an error.
messageProse, meant for a human reading a log. Some are fixed strings, some are written for the specific refusal. Do not branch on it.
error_codeAn integer. This is the field to branch on.
additional_dataOmitted unless the refusal has structured detail. On a validation failure it maps each rejected JSON field name to a reason.

There is no data wrapper, no meta block, no request id, and no string error code. Successful responses are shaped differently — see Response shapes.

Authentication failures do not use this envelope. They are produced by the gateway before your request reaches the API. See Authentication failures below.

Error codes

These are the codes reachable on /v1. Codes are stable; messages are not.

CodeHTTPWhat triggers it
19400Validation failed. A required field is missing (title, data, encryption_type, access_token), title exceeds 256 characters, description exceeds 1024, expired_at does not parse, encryption_type is anything other than e2ee-aes256-gcm, or an item_key_wrap was sent by a key whose custody level is none. additional_data names the offending fields; the last two cases carry a written explanation in message instead.
44400"Cannot parse request params" — the request body is not valid JSON, a DELETE arrived without a short code, or you used a method other than POST, GET or DELETE on a /v1/shares path.
104400"An Idempotency-Key header is required on this request" — the header was missing or whitespace only on a create.
105409The Idempotency-Key has already been used with a different request body.
106409The Idempotency-Key is in flight — an earlier request using it has not finished.
1404"Share not found" — unknown short code, already expired, or owned by another account. The three are deliberately indistinguishable so short codes cannot be probed.
53403Plan upgrade required. Raised when passcode_verifier is sent on a plan without view protection, or when the organization the key acts in has no owner linked to an active subscription.
61403"You have reached limit of your share creation." — the plan's share allowance is spent. See Share allowance.
76403The share is marked owner-only and the caller is not its owner.
78403"You do not have permission to perform this action on this team" — the key does not carry the scope the endpoint requires. Despite the wording, this is a scope problem, not a team problem.
107429"Rate limit exceeded". See Rate limits.
11500Unexpected internal failure. Retry it — and if it was a create, retry with the same Idempotency-Key, which is what stops a second copy of the secret being minted.
24500The user record behind the key could not be read while resolving your plan.
42500The share was found but could not be expired.
45500The organization behind the key could not be read while resolving your plan.
108503The billing lookup needed to check your share allowance failed or came back empty. This is deliberately a 503 and not an allow: retry it.

Codes from key management

Key management happens in the dashboard rather than through this API, so you will normally meet these in the interface. The codes are listed here so they can be looked up.

CodeHTTPWhat triggers it
10401"Login required" — the management call had no session.
98403"API access requires a Business or Enterprise plan" — minting a key on a plan without API access.
99404"API key not found" — unknown key id, or a key belonging to another account. Both give the same answer on purpose.
100403"You have reached the API key limit for your plan" — the plan's key allowance is fully used. Revoking a key frees its slot immediately.
101400A requested scope is outside the supported set.
102400The key name was empty.
103400The requested custody level is not one of 0, 1, 2.

Rate limits

Limits are expressed in requests per minute, sustained, with a burst ceiling of exactly twice the sustained rate. There is one number to configure per account; the burst follows from it.

PlanSustainedBurst
Business100 req/min200
Enterprise600 req/min1200
Everything elseNo API access

The sustained rate comes from your plan's api_rate_limit_rpm entitlement. A negotiated per-account limit can be set and overrides the plan value, so if your contract says something different, your contract is what applies. An account with no rate limit granted has no API access at all — an unset value means zero, never unlimited.

Two things are worth knowing about how requests are counted:

  • Two buckets are checked on every request — one for the API key, one for the account — and the stricter of the two decides. Minting more keys does not multiply your allowance; ten keys on a 100 rpm account share 100 rpm.
  • Every request that reaches the shares endpoints counts, including ones that then fail a scope check or validation. A tight retry loop on a 400 will eventually earn you a 429.

There is no daily, weekly or monthly request quota anywhere in the API. Rate is the only request-side limit.

The 429 response

HTTP/1.1 429 Too Many Requests
Retry-After: 12
{ "success": false, "message": "Rate limit exceeded", "error_code": 107 }

Retry-After is in whole seconds and is never 0 — the smallest value emitted is 1. It is the time until the stricter of your two buckets has a token available, so it is the longer of the two waits, not an average. Wait at least that long before retrying; the buckets refill continuously rather than resetting on a fixed window boundary, so retrying earlier will simply be refused again.

There are no rate-limit headers

The API does not emit X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, or any other X-RateLimit-* header. The only rate-limit header on this surface is Retry-After, and it appears only on a 429. If you have written a client that reads those headers to pace itself, it is reading nothing.

There is also no usage or quota endpoint to poll. To stay inside your limit, track your own send rate against the sustained figure for your plan, and treat a 429 with Retry-After as the authoritative signal to back off.

Share allowance

Request rate is not the only ceiling. Your plan also caps how many shares you may have created in the current period, and shares created through the API draw on the same allowance as shares created in the dashboard — there is one pool, not two. The number the dashboard shows as remaining shares is the number the API is working against.

When the allowance is spent, a create is refused:

{
  "success": false,
  "message": "You have reached limit of your share creation.",
  "error_code": 61
}

Three details matter in practice:

  • The allowance is checked before the encryption-type check. On an exhausted plan a create that is also wrong in some other way — plaintext encryption_type, for example — comes back as this 403, not as the 400 you were expecting. Fix the allowance first, then re-read the error.
  • When your key acts inside an organization, the organization's allowance is what applies, not the individual member's. A seat member of a paying team is judged by the team's plan.
  • A failed billing lookup is a refusal, not an allow. If the plan cannot be resolved, the create fails with 503 and error_code 108. Retry it; do not treat it as a permanent denial.

Plans whose share allowance is unlimited are never refused for this reason.

Key and webhook allowances

Two further ceilings come from your plan. Unlike the share allowance, these count what exists right now rather than what you have created this period, so revoking a key or deleting an endpoint frees its slot immediately.

PlanAPI keysWebhook endpoints
Business53
Enterprise2510
Everything else00

Minting a key beyond the allowance returns 403 with error_code 100. There is no equivalent API error for webhooks, because endpoints are created in the dashboard rather than through this API — you meet that ceiling there.

The two are counted on different scopes, which only shows up on a team:

  • Webhook endpoints are counted across the organization when your key acts inside one. A five-person team on Business shares three endpoints between them, not three each.
  • API keys are counted per user. Each member of that same team holds their own allowance of five.

A negotiated per-account override beats the plan value for either, the same way it does for the rate limit.

Authentication failures

Authentication is settled at the gateway, before your request reaches the API. That means authentication failures do not use the error envelope, and there are exactly two shapes.

No Authorization header at all401:

{ "message": "Unauthorized" }

A credential that cannot currently be used403:

{ "Message": "User is not authorized to access this resource with an explicit deny in an identity-based policy" }

Note the capital M in Message on the second one, and that neither body carries success or error_code. Parsing either as the standard envelope will fail.

A bad credential is not distinguishable from a permissions failure. Every one of these produces that same 403: a malformed token, an unknown key, a revoked key, a credential sent with its custody secret still attached, and a perfectly valid key on an account whose plan does not carry API access.

That is deliberate — a response that differed by cause would let anyone probe which key ids exist — but it does mean the response cannot tell you what went wrong. When you meet it, check in the dashboard, in this order: the key still exists and is not revoked; you are sending only the first two parts of the credential; and the account is on a plan with API access.

One useful discriminator: if you got the error envelope, your credential is fine. A missing scope is not an authentication failure — it reaches the API and comes back as 403 with error_code 78. So an envelope means you authenticated and were refused for some other reason; a bare Message body means you did not authenticate.

Timing

  • Revocation is immediate. The credential is verified on every single request, with no caching, so a revoked key stops working on its next call.
  • Entitlement changes take up to 30 seconds. The answer to "does this account have API access, and at what rate" is cached briefly, and denials are cached too. An account that has just been upgraded, or just had a negotiated limit applied, can keep seeing the old answer for up to half a minute. Wait, then retry.

Rate limits are per key and scale with your plan. See the limits by tier, or get in touch if you need a higher ceiling.