Errors
Status codes and the two response shapes, so a script can tell a bad request from a lost key.
# Errors
Status codes and the two response shapes, so a script can tell a bad request from a lost key.
Every error is JSON with a `detail` field, in one of two shapes. Match on the status code; the messages are written for people and may change.
## A refused request
For `400`, `401`, `403` and `404`, `detail` is one sentence:
```json
{"detail": "This API key is missing the 'tokens:write' scope."}
```
| Status | When |
|---|---|
| `400` | The request was well-formed and refused: a placement of nothing but whitespace, metadata past its limits, the plan's canary cap reached, or the provider would not do what was asked. |
| `401` | No key, an unknown key, or a key that has been deleted. See [Authentication](https://feintsecurity.com/docs/authentication#when-a-request-is-refused). |
| `403` | The key is valid but lacks the scope this endpoint needs. |
| `404` | No canary or event with that id in your organization, or `X-Feint-Client` names no client of yours. Ids are never shared across organizations, so another tenant's id is a `404` too. |
| `429` | Too many requests with this key, or from your address, in a short window. See [Rate limits](#rate-limits). |
## A malformed request
A `422` means the body or query could not be read as the endpoint expects: a missing field, the wrong type, a value out of range. `detail` is a list with one entry per problem, naming the field and what was wrong. The value you sent is not echoed back.
```json
{
"detail": [
{
"loc": ["body", "placement"],
"msg": "String should have at most 200 characters",
"type": "string_too_long"
}
]
}
```
`loc` is the path to the field: `body` for JSON fields, `query` for parameters. `type` is a stable, machine-readable code; `msg` is for people.
## Rate limits
Two limits apply, and both answer the same way: `429`, a JSON `detail`, and a `Retry-After` header saying how many seconds to wait.
| Counted | Limit | Where |
|---|---|---|
| Per API key | **100 requests in 10 seconds** | Feint, once the key is verified |
| Per source address, to `/v1` | **100 requests in any 10 seconds** | The edge, before a request reaches Feint |
| Per source address, requests with no valid key | **30 in 10 seconds** | Feint |
A pipeline that mints a decoy and reads back an alert is a handful of requests, so the limits are there for runaway scripts and scanners, not for normal use. The key limit is a burst of that many, then one more every 0.1 seconds; `Retry-After` says when the next request will be taken.
Two things follow from counting by address. Clients that share an egress address, such as hosted CI runners, share that budget, so a burst from one job can slow another on the same runner; the per-key limit is each job's own. And the limits are on requests, not on outcomes: refused requests count the same as successful ones, and a request with a wrong or missing key costs its address one of the 30.
On a `429`, wait for `Retry-After` and resend the same request; nothing was done with it. A client that polls alerts should poll no more than once a minute per organization, which is well inside every limit and matches how often the platforms report use.
Separately, the plan's canary cap is enforced on `POST /v1/tokens` and answers `400`, not `429`: it is a limit on what the organization may hold, not on how fast it asks.
## Everything else
A `5xx` means Feint could not finish. It has been logged; retry with backoff. A failed `POST /v1/tokens` does not leave a canary behind: if the record could not be written, the token is revoked at the provider before the error is returned.
## In short
- `401`: the key is wrong or gone. Fix the key; do not retry as is.
- `403`: the key needs another scope. Do not retry.
- `400`, `404`, `422`: the request needs changing. Do not retry unchanged.
- `429`: too fast, with this key or from this address. Wait for `Retry-After`, then retry unchanged.
- `5xx`: retry with backoff.
Every error is JSON with a detail field, in one of two shapes. Match on the status code; the messages are written for people and may change.
A refused request#
For 400, 401, 403 and 404, detail is one sentence:
{"detail": "This API key is missing the 'tokens:write' scope."}| Status | When |
|---|---|
400 |
The request was well-formed and refused: a placement of nothing but whitespace, metadata past its limits, the plan's canary cap reached, or the provider would not do what was asked. |
401 |
No key, an unknown key, or a key that has been deleted. See Authentication. |
403 |
The key is valid but lacks the scope this endpoint needs. |
404 |
No canary or event with that id in your organization, or X-Feint-Client names no client of yours. Ids are never shared across organizations, so another tenant's id is a 404 too. |
429 |
Too many requests with this key, or from your address, in a short window. See Rate limits. |
A malformed request#
A 422 means the body or query could not be read as the endpoint expects: a missing field, the wrong type, a value out of range. detail is a list with one entry per problem, naming the field and what was wrong. The value you sent is not echoed back.
{
"detail": [
{
"loc": ["body", "placement"],
"msg": "String should have at most 200 characters",
"type": "string_too_long"
}
]
}loc is the path to the field: body for JSON fields, query for parameters. type is a stable, machine-readable code; msg is for people.
Rate limits#
Two limits apply, and both answer the same way: 429, a JSON detail, and a Retry-After header saying how many seconds to wait.
| Counted | Limit | Where |
|---|---|---|
| Per API key | 100 requests in 10 seconds | Feint, once the key is verified |
Per source address, to /v1 |
100 requests in any 10 seconds | The edge, before a request reaches Feint |
| Per source address, requests with no valid key | 30 in 10 seconds | Feint |
A pipeline that mints a decoy and reads back an alert is a handful of requests, so the limits are there for runaway scripts and scanners, not for normal use. The key limit is a burst of that many, then one more every 0.1 seconds; Retry-After says when the next request will be taken.
Two things follow from counting by address. Clients that share an egress address, such as hosted CI runners, share that budget, so a burst from one job can slow another on the same runner; the per-key limit is each job's own. And the limits are on requests, not on outcomes: refused requests count the same as successful ones, and a request with a wrong or missing key costs its address one of the 30.
On a 429, wait for Retry-After and resend the same request; nothing was done with it. A client that polls alerts should poll no more than once a minute per organization, which is well inside every limit and matches how often the platforms report use.
Separately, the plan's canary cap is enforced on POST /v1/tokens and answers 400, not 429: it is a limit on what the organization may hold, not on how fast it asks.
Everything else#
A 5xx means Feint could not finish. It has been logged; retry with backoff. A failed POST /v1/tokens does not leave a canary behind: if the record could not be written, the token is revoked at the provider before the error is returned.
In short#
401: the key is wrong or gone. Fix the key; do not retry as is.403: the key needs another scope. Do not retry.400,404,422: the request needs changing. Do not retry unchanged.429: too fast, with this key or from this address. Wait forRetry-After, then retry unchanged.5xx: retry with backoff.