Skip to content

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 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.

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.

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.

{
  "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.

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.

  • 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_at and encrypt it again under the new key.

Your usage shows how close each key is to its cap.

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.

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.

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 unauthorized and 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, not 401.
  • Every request that gets past that point takes a token, whatever happens next: a firewall refusal, any other 403, a 429 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.

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.

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.