REST API
What every KeyGrant endpoint shares. Read it before you call the API without an SDK.
At a glance
- Base URL
https://api.keygrant.dev- Bodies
- JSON, in and out
- Times
- Unix milliseconds, except inside a lease (Unix seconds)
- CORS
- None, so a web page on another origin cannot read a response. Call the API from your app or your server.
Two kinds of endpoint
- Client endpoints: what your app calls. They are under
/v1/products/:slug/and need no API key: activate, validate, use, deactivate, trial, recover, key pickup and the public key. - Server endpoints: what your backend calls, with a secret
key from the dashboard's API keys page in
Authorization: Bearer <secret>. One of them,releases, is also under/v1/products/:slug/.
:slug is the product's API ID.
Requests
- An empty body is treated as
{}. -
Unknown fields are ignored, except by
GET /v1/licenses, which rejects unknown query parameters.
Errors
An error response has an HTTP status and a JSON body with an error code:
{ "error": "revoked" } | Error | Means |
|---|---|
400 invalid-json | The body is not JSON. |
400 invalid-request | The body or query does not fit the endpoint. The response does not say which field is wrong. |
401 unauthorized | A server endpoint was called without a valid secret key. |
404 unknown-product | No product has that API ID, or it is not yours. |
429 rate-limited | Past a limit: see Rate limits. |
500 internal | Something failed on KeyGrant's side. Retry. |
503 unavailable | A request that sends email, hands out keys or issues a license could not be counted against its limit, so it was not carried out. Retry after retryAfter seconds. |
Each endpoint's own error codes are listed on its page.
Rate limits
Past a limit, requests return 429 with retryAfter, the number of seconds
to wait, also sent as the Retry-After header:
{ "error": "rate-limited", "retryAfter": 23 } An IP address means one IPv4 address, or one IPv6 /64.
Per location
Every request is first checked against a per-minute limit kept at each Cloudflare location. These
limits are approximate, and a caller whose requests reach several locations is counted at each.
They are set far above what an app sends, and past one, retryAfter is 60.
| Endpoint | Limit | Counted per |
|---|---|---|
validate | 1,200 per minute | endpoint, product and IP address |
use | 1,200 per minute | endpoint, product and IP address |
use | 600 per minute | device (the key and its activationId), from any IP address |
trial | 1,200 per minute | endpoint, product and IP address |
pubkey | 1,200 per minute | endpoint, product and IP address |
activate | 60 per minute | endpoint, product and IP address |
deactivate | 60 per minute | endpoint, product and IP address |
recover | 60 per minute | endpoint, product and IP address |
pickup, pickup/ | 60 per minute | endpoint, product and IP address |
| Server endpoints | 1,200 per minute | secret key |
| All requests together | 6,000 per minute | IP address |
Counted exactly
A few requests are also counted exactly, across every location. Each of these limits resets at
the start of its window (a day starts at 00:00 UTC), and retryAfter is the number of
seconds until it does.
| Endpoint | Limit | Counted per |
|---|---|---|
trial | 10 per hour | product and IP address, for a request that would start a trial |
recover | 20 per hour | product and IP address |
pickup | 10 per 10 minutes | product and IP address |
pickup/ | 60 per 10 minutes | product and IP address |
pickup/ | 30 wrong codes per hour | product and email address |
POST /v1/licenses and POST /v1/test-licenses | 600 per minute, together | secret key |
POST /v1/test-licenses | 200 per day | account |
POST /v1/licenses | 1,000 per hour | account, for a request that emails the key |
-
The 30 wrong codes count across every code the email was sent; past them,
pickup/answersconfirm 429 too-many-attempts. - A replayed
Idempotency-Keyis not counted against the 200 test licenses a day. - A test license also lasts at most 30 days, and an account holds at most 25 active: see Testing your integration.
Emails a customer can set off
Past one of these limits, recover and pickup still return 200 and
no email is sent. Past the code limits, an activation on a key locked to its buyer returns 503 try-again and activates nothing (see Errors of
a key locked to its buyer). An address counts as one whatever its case.
| Limit | Counted per | |
|---|---|---|
recover's list of keys | 1 per 15 minutes | product and email address |
recover's list of keys | 5 per hour | email address, across all products |
pickup's code | 3 per 15 minutes | product and email address |
Two hourly limits count several emails together:
| Emails counted together | Your account | All of KeyGrant |
|---|---|---|
recover's list, a customer portal sign-in link, a key emailed from the portal, the Device limit reached email | 120 per hour | 600 per hour |
pickup's code, and the code of a key locked to its buyer | 600 per hour | 3,000 per hour |
A key locked to its buyer also limits its codes and wrong tries per key: see Rules of a locked key.
When a limit cannot be counted
- A trial already on record is not counted against the trial limit.
- If KeyGrant cannot count a client request, it goes through.
-
Except
recover,pickupandpickup/, which send email or hand out keys: when they cannot count the IP address's requests they returnconfirm 503 unavailable. KeyGrant never sends an email it could not count. -
The server endpoint
POST /v1/licensesreturns503 unavailableand issues nothing when it cannot count its request.
The email limit on POST /v1/licenses counts an issue with sendEmail and an
email, along with licenses issued from the dashboard with email on:
- Past it, or when it cannot be counted, the license is not issued.
- An issue whose email was then not sent is not counted.
- A retry with the idempotency key of a license already issued is neither counted nor refused by the email limit: it returns that license.
The API log
The dashboard's API logs page lists the requests made to your account in the last 30 days.
- Listed: every server request made with a valid secret key, including errors, and
every other client request. A request refused by
POST /v1/licenses's own limits is listed. - Not listed:
pubkeyrequests; client requests refused with429 rate-limited, or with503 unavailablewhen the request could not be counted; a server request refused by the 1,200 per minute limit on its secret key, since it is refused before its key is read. - Shown: a server request's bodies, and a client request's bodies only when the request failed, with license keys, leases and fingerprints masked. Each entry also keeps the caller's IP address and user agent.