Limits
rypt limits the size of each request, the number of operations a Free (free) key may
perform in a month, and how fast each API key may send requests. It also refuses some
calls on paid keys while your billing is not in good standing. This page gives each
limit as api.rypt.dev enforces it, the error you get when you reach it, and how to read
your usage.
Field sizes are counted after base64 decoding.
| What | Limit | Over the limit |
|---|---|---|
plaintext on encrypt |
1 to 65,536 bytes | Empty: 400 invalid_request. Larger: 413 payload_too_large |
dek you send to wrap |
16, 24 or 32 bytes | 400 invalid_dek_length |
dek that wrap generates when you send none |
32 bytes | |
aad on a Free or Extra wrapped (wrapped) key |
65,536 bytes | 413 payload_too_large |
aad on a Software (software) or Hardware (hsm) key |
65,520 bytes | 413 payload_too_large |
Hardware key: plaintext and aad together on encrypt, dek and aad together on wrap |
8,176 bytes | 413 payload_too_large |
ciphertext on decrypt and rewrap, wrapped on unwrap |
No limit of its own | The body limit applies |
| The request body, as sent | 128 KiB | 413 payload_too_large |
The field checks name the limit in their message, for example
plaintext must be at most 65536 bytes or
plaintext and aad together must be at most 8176 bytes for an hsm key.
On Software and Hardware keys, 16 of the 65,536 bytes Cloud KMS allows for associated
data carry your tenant id, which is why aad is limited to 65,520 bytes there. On a
Hardware key the same 16 bytes come out of Cloud KMS's limit of 8,192 bytes for the
plaintext and the associated data together, which leaves 8,176. With a 32-byte data key,
the largest aad that wrap accepts on a Hardware key is 8,144 bytes. The combined
limit is checked on encrypt and wrap only.
The tenant binding explains the
16 bytes.
There is no streaming and no batch call: each request carries one value. For data over 65,536 bytes, use envelope mode, or split the data and encrypt each part, one operation per part.
The request body
Section titled "The request body"The 128 KiB (131,072-byte) limit applies to the JSON body as sent, whitespace included,
on every route that takes a body. It is checked before rypt looks up the key, so a body
over it is refused with 413 payload_too_large, is not recorded in any audit log, and
is not counted. A refusal for a field over its own limit is not counted either.
Base64 makes a binary field a third larger: 65,536 bytes of plaintext is 87,384
characters. The largest plaintext and the largest aad therefore do not fit in one
request. With a 65,536-byte plaintext, the largest aad that fits in an encrypt body
written without spaces is 32,745 bytes.
A ciphertext can be too large to decrypt
Section titled "A ciphertext can be too large to decrypt"On a Free or Extra wrapped key, a ciphertext is 36 bytes longer than its plaintext. A
65,536-byte plaintext with a 32,745-byte aad makes an encrypt body of 131,069 bytes,
which fits. Its 65,572-byte ciphertext with the same aad makes a decrypt body of
131,118 bytes, which does not. With a 65,536-byte plaintext, the largest aad you can
still decrypt on these keys is 32,709 bytes.
On a Software key the ciphertext is Cloud KMS's own, longer than the plaintext by an
amount rypt does not set, so leave a wider margin. On a Hardware key, plaintext and
aad together are at most 8,176 bytes, which keeps its encrypt and decrypt bodies
far from this edge.
Size your requests by the decrypt body: 26 bytes of JSON,
{"ciphertext":"","aad":""}, plus the base64 of the ciphertext and of the aad. For
large data, use envelope mode, where only a data key
travels.
Operations
Section titled "Operations"An operation is one successful call to encrypt, decrypt, wrap, unwrap or
rewrap. rewrap is one operation, though it decrypts and then encrypts. Size does not
matter: a 1-byte plaintext and a 65,536-byte one are one operation each.
rypt counts an operation after its cryptography has succeeded. A refused or failed call is never counted, whatever its status, and neither is a failure in the key backend. Nothing else is counted: rotating, creating, renaming, deleting or restoring keys, listing them, and reading usage or the audit log do not add to any count.
One exception: if the operation succeeded but rypt could not write its audit row, the
response is 500 internal_error and the operation has been counted. A retry after a 500
can count twice.
Counts are kept per key and per calendar month in UTC. The month an operation belongs to is read from rypt's clock when the operation is checked. At 00:00 UTC on the 1st, every key starts the new month at zero.
| Tier | Operations a month | Past them |
|---|---|---|
Free (free) |
10,000 | Refused with 429 ops_cap_reached until the next month |
Extra wrapped (wrapped) |
50,000 included | Performed, and billed as overage |
Software (software) |
100,000 included | Performed, and billed as overage |
Hardware (hsm) |
250,000 included | Performed, and billed as overage |
Prices are on rypt.dev.
The Free tier's monthly cap
Section titled "The Free tier's monthly cap"A Free key may perform 10,000 operations in a UTC calendar month. Operations 1 to 10,000
are allowed. From then until 00:00 UTC on the 1st of the next month, every operation on
the key is refused with 429 ops_cap_reached. The cap cannot be raised.
The count belongs to the key. Every API key that uses the key adds to the same count,
and rotating the key does not reset it. Only API keys can call the five operations, so
only an API key ever gets 429 ops_cap_reached.
The response at the cap
Section titled "The response at the cap"{
"error": "ops_cap_reached",
"message": "monthly operation cap reached",
"request_id": "193a966d-e246-4efa-ad0f-d74b31f780ea",
"cap": 10000,
"resets_at": "2026-11-01T00:00:00Z"
}
| Field | Value |
|---|---|
error |
Always ops_cap_reached. |
message |
Always monthly operation cap reached. |
request_id |
The same value as the response's x-request-id header. |
cap |
The cap, as an integer: 10000. |
resets_at |
When the key's count starts again: 00:00 UTC on the 1st of the next month, as an RFC 3339 timestamp in UTC. |
cap and resets_at appear on no other error. The response has no Retry-After header
and no X-RateLimit-* headers; no response from rypt has them.
The cap is checked after the key is found and the request's fields are decoded and
checked, and before rypt tries to encrypt or decrypt anything. On a capped key, a
malformed body or field still gets its 400 or 413, and a request on a key pending
deletion still gets 409 key_pending_deletion. But a wrong aad or a damaged
ciphertext gets 429 ops_cap_reached, not 400 invalid_ciphertext, because rypt never
tries to open it.
How a request is decided gives the
full order. Each refusal at the cap is recorded in the key's
audit log, and is not counted.
The cap is checked before the operation and the count is added after it, in separate
steps. When several requests on one Free key are in flight as its count reaches 10,000,
all of them can pass the check, so the count can go a few operations past 10,000: at
most as many as were in flight at once. Those operations show in overage_ops in
your usage, and are not billed.
One Free key per tenant
Section titled "One Free key per tenant"A tenant can hold one Free key, in any of its containers. Creating a Free key while the
tenant has a live one, in any container, is refused with 409 free_key_exists:
{
"error": "free_key_exists",
"message": "this tenant already has a live free key",
"request_id": "5e71ba2a-7d9e-433e-b905-0686bfa9129e"
}
A Free key pending deletion still holds the place. Only destroying it frees the place: rypt destroys a deleted key once 7 days have passed since its deletion was requested, or at once when you use Delete now in the dashboard. Destroying a key makes everything encrypted under it undecryptable, and you cannot undo it. A new Free key has its own count, starting at zero.
What to do at the cap
Section titled "What to do at the cap"- Wait for
resets_at. Retrying earlier changes nothing: each retry is refused, is recorded in the audit log, and takes a token of your rate limit. - Use a paid tier for new data. A key's tier is fixed when the key is created, so this
means a new key, and creating one needs your tenant's billing in good standing (see
billing refusals); the tiers are on
rypt.dev. Data already encrypted under the Free key stays
under it, because no call moves a ciphertext to another key. Read it back after
resets_atand encrypt it again under the new key.
Your usage shows how close each key is to its cap.
Paid tiers
Section titled "Paid tiers"An Extra wrapped, Software or Hardware key includes a number of operations each month
(see the table under operations). Past them, every operation is still
performed, and is counted as overage, which is billed. Volume alone never makes rypt
refuse a paid key. A paid key that does not exist for the whole month includes a share
of its operations, split by the day: see allowance under
reading your usage.
Billing refusals
Section titled "Billing refusals"Paid keys need your tenant's billing to be in good standing: a subscription that is
active, in a trial, or past due while a failed payment is retried. With no subscription,
or one in any other state, rypt refuses some calls with 402 billing_required.
| Call | Refused while billing is not in good standing |
|---|---|
encrypt, wrap, rewrap on a paid key |
Yes |
decrypt, unwrap on a paid key |
Never |
| Creating a paid key | Yes |
| Any call on a Free key, and creating one | Never |
| Rotating, renaming, deleting, restoring and listing keys, reading usage and the audit log | Never |
On encrypt, wrap and rewrap the body is:
{
"error": "billing_required",
"message": "...",
"request_id": "6441904d-9771-4cb2-ab8b-c54fe8f18563"
}
with the message
new ciphertext under a paid key needs this tenant's billing in good standing; decrypt and unwrap still work.
Creating a paid key gets the message
a paid key needs this tenant's billing in good standing. The refusal on encrypt,
wrap and rewrap comes after the request's fields are checked and before any
cryptography; it is recorded in the key's audit log and is not counted.
Set up or fix billing on the Billing page of the
dashboard. Rotating a paid key is not refused for billing,
but rewrap is: read
rewrap moves ciphertext to the primary version
before you rotate a key while billing has lapsed.
The rate limit
Section titled "The rate limit"Each API key has a token bucket that holds up to 100 tokens, starts full, and refills
continuously at 100 tokens a second. Each request takes one token. With none left, rypt
refuses the request with 429 rate_limited, and the refused request takes no token:
{
"error": "rate_limited",
"message": "too many requests for this api key",
"request_id": "67070b41-86e0-4496-b8d4-d280ccc930e9"
}
Each server process keeps its own buckets, and processes do not share them. api.rypt.dev
can run several processes, and you do not choose which one serves a request, so how
many requests a busy API key gets through before its first 429 varies. Do not count
on more than a burst of 100 and 100 requests a second for each API key. Each API key has
its own bucket; API keys do not share one.
For an API key, the token is taken during authentication, once rypt has found the API key by its prefix and before it checks the secret. So:
- A malformed token, or one whose prefix names no API key or a revoked one, gets
401 unauthorizedand takes no token. - A request with a real prefix and a wrong secret takes a token. Once the bucket is
empty, it gets
429 rate_limited, not401. - Every request that gets past that point takes a token, whatever happens next: a
firewall refusal, any other
403, a429 ops_cap_reached, or success.
The limit applies to every route an API key can call, GET /v1/usage and the key and
audit routes included, but not to the health check. A dashboard session has a bucket of
the same size for each signed-in user, taken after the session is verified, with the
same message.
A 429 rate_limited is not recorded in any audit log and is not counted as an
operation.
Telling the two 429s apart
Section titled "Telling the two 429s apart"Both have status 429. Read the error field.
rate_limited |
ops_cap_reached |
|
|---|---|---|
| Cause | Too many requests from one API key, or one dashboard user, in a short time | A Free key has used its 10,000 operations this UTC month |
| Applies to | Every route an API key calls, and dashboard sessions | encrypt, decrypt, wrap, unwrap and rewrap on a Free key |
| Extra fields | None | cap, resets_at |
Retry-After or X-RateLimit-* headers |
None | None |
| In the key's audit log | No | Yes |
| Clears | As the bucket refills, at 100 tokens a second | At resets_at |
| What to do | Wait and retry, backing off exponentially with jitter. If it recurs, send fewer requests a second. | Do not retry before resets_at. See what to do at the cap. |
Reading your usage
Section titled "Reading your usage"GET /v1/usage returns one month's counts for each key you can see.
curl -sS "https://api.rypt.dev/v1/usage?period=2026-10" \
-H "Authorization: Bearer $RYPT_TOKEN"
period is a UTC month as YYYY-MM. Leave it out for the current month. A malformed
or empty period is refused with 400 invalid_period and the message
period must be a month of the form YYYY-MM.
The response is a JSON array with one object per key, oldest key first:
[
{
"key_id": "0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0",
"container_id": "3e2d1c0b-9a87-4654-b321-0fedcba98765",
"ops": 9412,
"overage_ops": 0,
"cap": 10000,
"allowance": 10000
},
{
"key_id": "b3584f0d-6c13-43f0-a0d6-ea6daf127822",
"container_id": "3e2d1c0b-9a87-4654-b321-0fedcba98765",
"ops": 1203,
"overage_ops": 0,
"cap": 100000,
"allowance": 70967
}
]
| Field | Meaning |
|---|---|
key_id |
The key. |
container_id |
The container the key is in. |
ops |
Operations performed in the month, including any past allowance. |
overage_ops |
How many of ops were past allowance. On a paid key these are billed. On a Free key it is 0, except for the few operations concurrent requests can carry past the cap, which are not billed. |
cap |
The tier's number of operations a month: 10,000, 50,000, 100,000 or 250,000. |
allowance |
The operations included in the month: cap, except for a paid key that does not exist for the whole month. |
A Free key's allowance is always 10,000, whatever day it was created. For a paid key,
allowance is cap times the UTC days the key exists in the month, divided by the
days in the month, rounded down. Its days run from the day it was created, or the 1st,
through the day its deletion was requested, or the month's last day, both counted, so
this month's figure already includes the days still to come. The second line above is a
Software key created on 10 October 2026: 22 of October's 31 days, so it includes
100,000 x 22 / 31 = 70,967 operations. If a key's deletion is requested in one month
and the key is restored in a later one, the earlier month's allowance can still stop
at the day of the request, because a month that has ended is not reopened. For months
before October 2026, allowance is always cap.
What is left this month is allowance - ops. On a Free key, once that is 0 or less,
operations are refused until 00:00 UTC on the 1st of the next month. On a paid key,
operations past it are billed as overage. The response has no resets_at and no
endpoint returns a remaining count: compute the reset as the first instant of the next
UTC month.
The lines cover every key that is live now, keys pending deletion included, with zeros for a key that did nothing in the month, even one created after it. A destroyed key appears only for a month in which it performed operations. An API key sees the keys of its own container, and only its listed keys if it is restricted to some (access control). A dashboard session sees every key of the tenant.
Reading usage is not counted and not recorded in any audit log. It takes a rate-limit token like any other request.