Skip to content

Fryri documentation

Authentication and keys

Updated 2026-09-26

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/memories?end_user_id=user_42&limit=2" \
  -H "Authorization: Bearer $FRYRI_API_KEY"

Response

{
  "memories": [
    {
      "id": "file_tjSa4DvE15aMARb4",
      "type": "document",
      "title": "Example Domain.html",
      "status": "ready",
      "status_reason": null,
      "created_at": "2026-09-25T09:03:01.562079Z",
      "source_ref": "https://example.com",
      "sha256": "ff67a9d764d6a2367a187734e697f6a53217db9a21c101d410a113ca871a299d"
    },
    ...
  ],
  "next_cursor": "eyJwIjp7ImkiOjQ1OSwiayI6..."
}

Keys#

The console shows a key once, when it is created. A key works on the library of its own account and on the end users of that account. A key revoked in the console stops at once, and its next request fails with 401 unauthorized.

It's recommended to keep keys on your server, in an environment variable such as FRYRI_API_KEY.

Each key can carry a monthly spend cap, set in the console. A key over its cap fails with 429 key_budget_reached.

Billing#

Every call spends from the prepaid credit of the account, separate from any Fryri plan. A newly verified account starts with $1 of credit. Every call needs credit, including the free ones. Without it, the call fails with 402 insufficient_credits.

Top-ups, usage and the request log are in the developer console. The price list covers each call, and POST /v1/search and POST /v1/answer report their cost in cost_usd.

Rate limits#

Each key has a limit of requests per minute, set by the plan of its account:

PlanRequests per minute
Free30
Standard60
Pro120
Premium240
Any plan, with prepaid creditAt least 120

A request over the limit fails with 429 rate_limited. The Retry-After header gives the seconds to wait.

Errors#

Every error returns one envelope. error.code is stable and machine-readable, and error.message is prose that 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, and the X-Request-ID header carries the same value.

HTTPcoderetryableRecovery
4xx
401unauthorizedfalseSend a live key as Authorization: Bearer $FRYRI_API_KEY.
401sandbox_discontinuedfalseCreate a live key in the developer console. Sandbox keys no longer work.
401account_deletedfalseLog in to restore the account. Its keys then work again.
402insufficient_creditsfalseAdd credit in the developer console, then retry.
404not_foundfalseCheck the id, and send the end_user_id the memory belongs to.
405method_not_allowedfalseUse the method that error.message names.
409conflictfalseRead the current state, then resend.
409idempotency_key_reusedfalseSend a new Idempotency-Key for a new request.
410endpoint_retiredfalseCall the endpoint in error.use.
413file_too_largefalseSend a smaller file. error.message names the limit.
413request_too_largefalseSend a smaller request body. error.message names the limit.
422invalid_requestfalseFix the field named in error.message ({field}: {reason}), or remove a field the call does not define. error.errors lists every problem.
422unsupported_formatfalseAdd text or a document. Photos, video and audio can't be added.
422invalid_cursorfalseRead the memory again without cursor.
429rate_limitedtrueWait for the Retry-After header, then retry.
429key_budget_reachedfalseRaise or clear the spend cap of the key in the developer console, or use another key.
5xx
500internal_errortrueRetry once. Quote request_id if it persists.
502upstream_errortrueRetry.
502ingest_failedtrueRetry the add with the same Idempotency-Key.
503unavailabletrueFryri is busy. Retry after a few seconds.

An add that the upload pipeline refuses carries that pipeline's own code, HTTP status and retryable, such as storage_quota_exceeded (413) or upload_rate_limit (429, with Retry-After). A streamed answer reports a failure after its first event as an event of "type": "error", with the same code, message and retryable fields.

A call rejects any field it does not define with 422 invalid_request, and error.message names the field. A typo such as enduser_id fails at once instead of adding to the wrong library.

Unauthenticated request

curl -X POST https://api.fryri.com/v1/search \
  -H "Content-Type: application/json" \
  -d '{"query": "x"}'

Response

{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key. Pass it as 'Authorization: Bearer fryri_sk_...'.",
    "retryable": false
  },
  "request_id": "b4de2b67719241b4b319d6f8fc7cb0d4"
}

Unknown field

curl -X POST https://api.fryri.com/v1/memories \
  -H "Authorization: Bearer $FRYRI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Project Atlas uses Python and FastAPI.", "enduser_id": "user_42"}'

Response

{
  "error": {
    "code": "invalid_request",
    "message": "enduser_id: Extra inputs are not permitted",
    "retryable": false,
    "errors": [
      {
        "type": "extra_forbidden",
        "loc": [
          "body",
          "enduser_id"
        ],
        "msg": "Extra inputs are not permitted",
        "input": "user_42",
        "url": "https://errors.pydantic.dev/2.10/v/extra_forbidden"
      }
    ]
  },
  "request_id": "92fa7a87447044adafc2b36aa8a50203"
}

The machine-readable contract#

  • OpenAPI spec: the source of truth for every call. Generate a typed client from it, or hand it to an agent.
  • Interactive docs: try every call in the browser.

A breaking change to a /v1 call ships as a new path. A path from the earlier API fails with 410 endpoint_retired, and the migration table names each replacement.

Tip: Branch on error.code and error.retryable, never on error.message.

Warning: A key in browser code is visible to every visitor. Keep keys on your server.