A valid request URL is required to generate request examples{
"granted": true,
"duplicate": true,
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"balance": 123,
"metric": "<string>"
}{
"granted": true,
"duplicate": true,
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"amount": 123,
"balance": 123,
"level_reached": {
"name": "<string>",
"rank": 123
},
"achievements_unlocked": [
"<string>"
],
"metric": "<string>"
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}Grant points by hand
Your backend awarding points directly: support compensating somebody, a migration bringing balances in from whatever you had before, a campaign paying for something that happens outside your product.
This is the one write in the module that is not a fact landing, and it is on the API-key surface for that reason — this is a program you control, while a widget is a page you ship. The line lands with source: "manual", which is the column to group by when a balance looks impossible.
request_key is required, unlike the optional handle on POST /v1/usage. A retried grant with no key is a second gift, and the caller that most wants this endpoint is a script somebody runs twice. A replay answers 200 with duplicate: true and the balance as it stands — the state you asked for is true, so an error would be a retry to handle for a correct outcome.
Name the recipient with contact_id or with your own external_id — one of the two, never both. The key’s environment scopes it, so a live external_id asked with a test key answers 404.
No rule and no ceiling apply: ceilings belong to rules that fire on their own, and the caller here is a person deciding. An anonymous contact is refused — a balance on a visitor belongs to a page load rather than to a person.
The response says what the grant set off, so you do not have to subscribe to a webhook to find out whether the points you just gave meant something.
With metric, the amount credits that tally instead of the balance — “this customer completed 3 courses in the migration” — and balance in the answer is that metric’s tally. A metric credit cannot unlock a points_total badge, because it did not move a balance.
A valid request URL is required to generate request examples{
"granted": true,
"duplicate": true,
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"balance": 123,
"metric": "<string>"
}{
"granted": true,
"duplicate": true,
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"amount": 123,
"balance": 123,
"level_reached": {
"name": "<string>",
"rank": 123
},
"achievements_unlocked": [
"<string>"
],
"metric": "<string>"
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}Autorizações
An organization API key, uk_sk_live_… or uk_sk_test_…. The environment is resolved from the stored key row on every request, never from a request parameter.
Every route behind this credential is rate limited per key — 1000 requests a minute — with a second ceiling of 3000 a minute across all the keys of one environment, so splitting your traffic across keys isolates it and minting more keys does not buy more of it. A test key can never spend a live key's allowance. Every response carries the current state in headers; see the rate-limits guide.
Cabeçalhos
A key you choose — a UUID, or your own identifier for the operation — that makes this write safe to retry. Send the same key again and you get the same response back: the same status, the same body, and Idempotent-Replay: true. The handler does not run a second time.
The key is scoped to your API key and the route, so keys never collide between environments or tenants. It is remembered for 24 hours. Reusing it for a different request is refused (422), and a second request arriving while the first is still running is refused too (409) — retry that one in a moment.
A request that failed with a 5xx or was rate limited leaves the key spendable: retry it with the same key.
255Corpo
Signed and never zero. Negative is a correction.
-10000 <= x <= 10000What this line says on their own statement. Write it for them to read.
200Required. Your handle for this grant. The same key never writes a second line.
200The recipient. One of this or external_id.
Your own id for them. One of this or contact_id.
The KEY of a metric to credit instead of the balance. Absent is the balance, which is what this endpoint did before metrics existed. It is a field here rather than a second endpoint because it is the same act: the same recipient, the same required request_key, the same line in the same ledger.