An API key authenticates every request to https://api.fryri.com/v1. Create a key in the developer console, then send it as a Bearer token in the Authorization header. A request without a live key fails with 401 unauthorized.
Request
curl https://api.fryri.com/v1/usage \
-H "Authorization: Bearer $FRYRI_API_KEY"Response
{
"key": {
"scope": "key",
"api_key_id": 104,
"period_days": 30,
"total_tokens": 89209,
"total_cost_usd": 0.06452,
"total_requests": 17,
"by_request_type": [
{"request_type": "api_ask", "tokens": 0, "cost_usd": 0.06, "requests": 1},
{"request_type": "api_search", "tokens": 0, "cost_usd": 0.0025, "requests": 1},
...
]
},
"account": {
"scope": "account",
"api_key_id": null,
"period_days": 30,
...
}
}Billing#
API spend comes from your prepaid credit balance. GET /v1/wallet shows the balance and GET /v1/usage shows spend; both stay reachable even when the balance is empty, so you can always check state and top up.
Errors#
Every error under /v1 returns one envelope. error.code is stable and machine-readable; error.message is prose and may change. error.retryable says whether the same call, sent again unchanged, can succeed. request_id is the id to quote when reporting a problem; the X-Request-ID header carries the same value.
| HTTP | code | retryable | Recovery |
|---|---|---|---|
400 | bad_request | false | Fix the request as error.message describes. |
401 | unauthorized | false | Send a live key as Authorization: Bearer $FRYRI_API_KEY. |
402 | insufficient_credits | false | Add credit through POST /v1/wallet/topup-link (echoed in error.topup_endpoint), then retry. |
403 | forbidden | false | The key cannot act on this resource; use the owning account's key. |
404 | not_found | false | Check the id; a deleted resource reads as missing. |
409 | conflict | false | Read the current state, then resend. |
422 | invalid_request | false | Fix the field named in error.message ({field}: {reason}); error.errors lists every violation. |
429 | rate_limited | true | Wait for the Retry-After header, then retry. |
500 | internal_error | true | Retry once; quote request_id if it persists. |
502 | upstream_error | true | Retry. |
503 | unavailable | true | Retry after a pause. |
504 | upstream_timeout | true | Retry. |
On a streaming POST /v1/chat ("stream": true) an error after the first byte arrives as an event of "type": "error" with the same error.code and error.message fields. Its codes are byok_key_rejected (your provider refused the key you passed) and byok_request_rejected (your provider refused the request, usually a wrong model slug). upstream_error means the model returned an error or the stream ended early; internal_error is a failure on Fryri's side.
Unauthenticated request
curl https://api.fryri.com/v1/usage{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key. Pass it as 'Authorization: Bearer fryri_sk_...'.",
"retryable": false
},
"request_id": "ed1de53e1bf7413891649e7e825f5629"
}Invalid body
curl -X POST https://api.fryri.com/v1/capture \
-H "Authorization: Bearer $FRYRI_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'{
"error": {
"code": "invalid_request",
"message": "Provide exactly one of `text` or `content_base64`.",
"retryable": false
},
"request_id": "b73721361a2c406fa8d36ee66dbd13c1"
}The machine-readable contract#
- OpenAPI spec: the source of truth for the whole surface. Generate a typed client from it, or hand it to an agent.
- Interactive docs: try every endpoint in the browser.
- Python client and TypeScript client: single-file clients with no framework lock-in.
Endpoints under /v1/evolving (beta) may change. Every other endpoint is stable.