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:

403
{ "error": "revoked" }
ErrorMeans
400 invalid-jsonThe body is not JSON.
400 invalid-requestThe body or query does not fit the endpoint. The response does not say which field is wrong.
401 unauthorizedA server endpoint was called without a valid secret key.
404 unknown-productNo product has that API ID, or it is not yours.
429 rate-limitedPast a limit: see Rate limits.
500 internalSomething failed on KeyGrant's side. Retry.
503 unavailableA 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:

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

EndpointLimitCounted per
validate1,200 per minuteendpoint, product and IP address
use1,200 per minuteendpoint, product and IP address
use600 per minutedevice (the key and its activationId), from any IP address
trial1,200 per minuteendpoint, product and IP address
pubkey1,200 per minuteendpoint, product and IP address
activate60 per minuteendpoint, product and IP address
deactivate60 per minuteendpoint, product and IP address
recover60 per minuteendpoint, product and IP address
pickup, pickup/confirm60 per minuteendpoint, product and IP address
Server endpoints1,200 per minutesecret key
All requests together6,000 per minuteIP 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.

EndpointLimitCounted per
trial10 per hourproduct and IP address, for a request that would start a trial
recover20 per hourproduct and IP address
pickup10 per 10 minutesproduct and IP address
pickup/confirm60 per 10 minutesproduct and IP address
pickup/confirm30 wrong codes per hourproduct and email address
POST /v1/licenses and POST /v1/test-licenses600 per minute, togethersecret key
POST /v1/test-licenses200 per dayaccount
POST /v1/licenses1,000 per houraccount, for a request that emails the key
  • The 30 wrong codes count across every code the email was sent; past them, pickup/confirm answers 429 too-many-attempts.
  • A replayed Idempotency-Key is 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.

EmailLimitCounted per
recover's list of keys1 per 15 minutesproduct and email address
recover's list of keys5 per houremail address, across all products
pickup's code3 per 15 minutesproduct and email address

Two hourly limits count several emails together:

Emails counted togetherYour accountAll of KeyGrant
recover's list, a customer portal sign-in link, a key emailed from the portal, the Device limit reached email120 per hour600 per hour
pickup's code, and the code of a key locked to its buyer600 per hour3,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, pickup and pickup/confirm, which send email or hand out keys: when they cannot count the IP address's requests they return 503 unavailable. KeyGrant never sends an email it could not count.
  • The server endpoint POST /v1/licenses returns 503 unavailable and 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: pubkey requests; client requests refused with 429 rate-limited, or with 503 unavailable when 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.