Errors
Every error rypt returns is a JSON object with a code, a message and the request's id.
Branch on the code, in the error field. The message is for people: several messages
carry details of the request, such as a field name, a limit, an address or a time, so
do not parse them.
The error body
Section titled "The error body"{
"error": "unauthorized",
"message": "missing or invalid credentials",
"request_id": "orders-7f3a9c"
}
| Field | Type | Value |
|---|---|---|
error |
string | The error code, one of those in every error. |
message |
string | A description of the error, for people. |
request_id |
string | The request's id, the same value as its x-request-id header. |
cap |
integer | Only on 429 ops_cap_reached: the monthly cap, 10000. |
resets_at |
string | Only on 429 ops_cap_reached: when the count starts again, as an RFC 3339 timestamp in UTC. |
Every other error has exactly the first three fields. The response's content type is
application/json. The response at the cap
shows a full ops_cap_reached body.
A 500 internal_error always has the message internal error. rypt logs its cause under
the request id and never returns it.
The request id
Section titled "The request id"Every response, success or error, carries an x-request-id header, and an error body
repeats it as request_id. The audit log
stores it in the row a request writes. When you report a problem to support@rypt.dev,
include the id, and never an API key.
You can choose the id. If your request has an x-request-id header of 1 to 128
characters, each an ASCII letter or digit, _, ., : or -, rypt uses it as the
request's id. Any other value is dropped, and rypt generates a UUID instead. rypt does
not check that your ids are unique.
curl -sS -i https://api.rypt.dev/v1/keys -H "x-request-id: orders-7f3a9c"
This request has no Authorization header, so rypt refuses it with
401 unauthorized and the body shown under the error body, and the
response's x-request-id header is orders-7f3a9c.
Every error
Section titled "Every error"When a request has more than one problem, the first check to fail decides the answer;
how a request is decided gives the
order. "Dashboard:" marks codes that only a dashboard session can get. Which errors
leave a row in a key's audit log is set out under
what is recorded, and for 403 refusals under
which refusals are recorded.
| Status | Code | When | Retry? |
|---|---|---|---|
| 400 | invalid_request |
The body is not JSON or has the wrong shape (see routing and body errors), or plaintext on encrypt decodes to 0 bytes. |
No |
| 400 | invalid_base64 |
A binary field is not standard base64 with = padding. The message names the field. |
No |
| 400 | invalid_dek_length |
The dek sent to wrap does not decode to 16, 24 or 32 bytes. |
No |
| 400 | invalid_ciphertext |
decrypt, unwrap or rewrap cannot open the value: a different aad, a changed value, another key's value on a Free or Extra wrapped key, or a key version that is no longer enabled. See when the aad does not match and rotation. |
No |
| 400 | invalid_name |
A name is empty, only whitespace, over 128 characters, or has a control character. | No |
| 400 | invalid_tier |
tier on POST /v1/keys is not exactly free, wrapped, software or hsm. |
No |
| 400 | invalid_container |
container_id names no container the request may use. An API key may use only its own. |
No |
| 400 | invalid_query |
since, until or limit on GET /v1/keys/{id}/audit is malformed or out of range, or the query string of that route or of GET /v1/usage cannot be read, for example because a parameter is repeated. |
No |
| 400 | invalid_period |
period on GET /v1/usage is present but is not a month written YYYY-MM. |
No |
| 400 | invalid_allowed_actions |
Dashboard: an API key's actions include a name that is not an action, or are null on a change. |
No |
| 400 | invalid_key_ids |
Dashboard: an API key's key list is empty, names over 100 keys, or names something that is not a live key in the API key's container. | No |
| 400 | invalid_firewall |
Dashboard: firewall_id is not a firewall in the API key's container. |
No |
| 400 | invalid_allowed_ips |
Dashboard: a firewall's list is empty, names over 64 networks, or has an entry rypt does not accept. See firewalls. | No |
| 401 | unauthorized |
The credential is missing, malformed, unknown, revoked or wrong, or a dashboard session did not verify (for example, it expired). Every cause gets the same message. | No |
| 402 | billing_required |
Creating a paid-tier key, or encrypt, wrap or rewrap on one, while the tenant's billing is not in good standing. See billing refusals. |
After billing is fixed |
| 403 | api_key_required |
A dashboard session called encrypt, decrypt, wrap, unwrap or rewrap, which take an API key only. |
No |
| 403 | session_required |
An API key called a route that only a dashboard session may use. See dashboard sessions. | No |
| 403 | ip_not_allowed |
The API key is held to a firewall, and the request came from outside its networks. The message names the address rypt saw. | Not from that address |
| 403 | key_not_allowed |
The API key has a key list and the key in the path is not on it, or it tried to create a key, which an API key with a key list cannot do whatever its actions. | No |
| 403 | action_not_allowed |
On one of the ten routes that check actions, the API key was not given the action the route needs. What actions do not restrict lists the routes and their actions. | No |
| 403 | no_tenant |
Dashboard: the signed-in user belongs to no tenant. | No |
| 404 | not_found |
The path is not a route, or its id is not a UUID or names nothing the caller can reach: unknown, destroyed (its audit log stays readable), another tenant's, or, for an API key, in another container. | No |
| 405 | method_not_allowed |
The path is a route, but not with this method. | No |
| 409 | key_pending_deletion |
The key is pending deletion, so it refuses the five operations, rotation and renaming. The message says when the key will be destroyed, or, once that time has passed, that it can no longer be restored. | After a restore, while one is possible |
| 409 | key_exists |
Another live key in the container, pending deletion included, has that name. | No |
| 409 | free_key_exists |
The tenant already has a live Free (free) key; one pending deletion counts. See one Free key per tenant. |
No |
| 409 | not_pending_deletion |
POST /v1/keys/{id}/restore on a key that is not pending deletion. |
No |
| 409 | restore_window_closed |
A restore after the key's destroy_after has passed. The key can no longer be restored. |
No |
| 409 | container_exists |
Dashboard: another live container of the tenant has that name. | No |
| 409 | container_is_default |
Dashboard: deleting the default container. | No |
| 409 | container_not_empty |
Dashboard: deleting a container that still holds keys (pending deletion included), live API keys or firewalls. | No |
| 409 | firewall_exists |
Dashboard: another live firewall in the container has that name. | No |
| 409 | firewall_in_use |
Dashboard: deleting a firewall that live API keys are held to. | No |
| 409 | billing_active |
Dashboard: starting a checkout while the tenant has a subscription open. | No |
| 409 | billing_not_set_up |
Dashboard: opening the billing portal before billing was set up. | No |
| 413 | payload_too_large |
The body is over 128 KiB, or a field is over its size limit after decoding. See sizes. | No |
| 415 | unsupported_media_type |
A route that takes a JSON body got a request without a JSON Content-Type. |
No |
| 429 | rate_limited |
The API key, or the dashboard user, has used up its rate limit. See the rate limit. | Yes, after a delay |
| 429 | ops_cap_reached |
A Free key has used its 10,000 operations this UTC month, so all five operations are refused. See the Free tier's monthly cap. | Not before resets_at |
| 500 | internal_error |
rypt failed, or its database, Cloud KMS or Stripe did. A Cloud KMS outage is a 500, not a 503. |
See retrying |
| 503 | unavailable |
rypt was too busy to start the request in time (too many API key checks, key changes or new API keys in flight), or got no answer from something it needed: the check of the API key's actions, the dashboard's sign-in provider, or Stripe. The message says which. | Yes, after a delay |
Routing and body errors
Section titled "Routing and body errors"These answer a request rypt cannot route, or a body it cannot read. The four body
messages are the text of the web framework rypt is built on, and the two 400 messages
end with its description of the problem:
| Request | Answer | message |
|---|---|---|
| A path that is not a route | 404 not_found |
not found |
| A route with a method it does not take | 405 method_not_allowed |
method not allowed |
| A JSON body without a JSON content type | 415 unsupported_media_type |
Expected request with `Content-Type: application/json` |
| A body over 128 KiB | 413 payload_too_large |
Failed to buffer the request body: length limit exceeded |
| A body that is empty or not valid JSON | 400 invalid_request |
Failed to parse the request body as JSON: , then the description |
| JSON of the wrong shape, such as a missing field or a number where a string belongs | 400 invalid_request |
Failed to deserialize the JSON body into the target type: , then the description |
For example, {} sent to encrypt gets
Failed to deserialize the JSON body into the target type: missing field `plaintext` at line 1 column 2,
and {"plaintext":1} gets
Failed to deserialize the JSON body into the target type: plaintext: invalid type: integer `1`, expected a string at line 1 column 14.
- A path that is not a route answers
404 not_found, and a route with the wrong method405 method_not_allowed, whatever the credentials. - The content type is checked before the body is read, so a body sent without it gets
415whatever its size or contents.application/json; charset=utf-8, andapplication/types ending in+jsonsuch asapplication/merge-patch+json, are accepted;text/jsonis not. - A route that takes a JSON body needs one even when every field is optional: to have
wrapgenerate a data key, send{}. With no body, the request gets415without a content type, and400with one, with the messageFailed to parse the request body as JSON: EOF while parsing a value at line 1 column 0. - JSON of the wrong shape is
400, not422. - Fields rypt does not know are ignored, so a misspelled optional field is not an error:
see a misspelled field means no aad.
Only
PATCH /v1/api-keys/{id}andPATCH /v1/firewalls/{id}refuse unknown fields, with400 invalid_request. - On a route with a key id in its path, the body is read after authentication and after
the checks of the path, the API key's key list and, where they apply, its actions. A
refused request gets its
403even with a malformed body, and an unknown id with a malformed body gets400, not404: see how a request is decided. POST /v1/keysreads its body first, before the API key's key list and actions are checked. A body it cannot read gets400 invalid_request,413 payload_too_largeor415 unsupported_media_typeeven from an API key that would be refused. With a well-formed body,403 key_not_allowedor403 action_not_allowedcomes beforenameandtierare checked: see how a request is decided.
Retrying
Section titled "Retrying"rypt sends no retry hints: no response has a Retry-After header or X-RateLimit-*
headers. Choose your own delays, backing off exponentially with jitter. rypt has no
idempotency keys.
429 rate_limitedand503 unavailableare the codes to retry, after a delay.429 ops_cap_reachedis refused again on every retry untilresets_at. See what to do at the cap.- Every other
4xxrepeats until something changes: the request, the API key's settings (a change applies from its next request, see when changes take effect), the tenant's billing, or the key's state.