Skip to content

Developer documentation

The API reference.

The Fryri API stores the notes, documents and conversations your app adds. It finds them again by meaning or by exact words, and it answers questions with citations and retrieved sources. It keeps a private library for each of your users, all under one account.

CallWhat it doesPrice
POST /v1/memoriesAdds a note, document or conversation snapshot.The model cost of reading it
GET /v1/memoriesLists memories, newest first.Free
GET /v1/memories/{memory_id}Returns one memory with its status, its text and its original file.Free
DELETE /v1/memories/{memory_id}Deletes one memory.Free
POST /v1/searchReturns the memories that match a query, best match first.$0.0025 per call
POST /v1/answerAnswers a question with citations and retrieved context.$0.02 per answer
DELETE /v1/end-users/{end_user_id}Erases one end user and all their memories.Free

Each of these calls starts at https://api.fryri.com/v1. A breaking change to one of them ships as a new path.

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.

Browse endpoints

Quickstart

The quickstart adds a memory for one of your users, waits until search can find it, then searches and asks a question. Each step is one call.

  1. Create a key in the developer console. A newly verified account starts with $1 of credit. Export the key as FRYRI_API_KEY:
    export FRYRI_API_KEY="fryri_sk_..."
  2. The first add with an end_user_id creates the private library of that user. The memory comes back with its id and the status processing. Add a memory:
    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. The launch review is on 14 October.",
        "title": "Project Atlas notes",
        "source_ref": "crm:note:881",
        "end_user_id": "user_42"
      }'
  3. Search and answers find a memory once its status is ready. A short note is usually ready within seconds. Read the memory until then:
    curl "https://api.fryri.com/v1/memories/file_NkolfLrElMmM2fCn?end_user_id=user_42" \
      -H "Authorization: Bearer $FRYRI_API_KEY"
  4. Each search result is a memory with its most relevant passage in text and a score in [0, 1]. Search the library of the user:
    curl -X POST https://api.fryri.com/v1/search \
      -H "Authorization: Bearer $FRYRI_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "query": "When is the Atlas launch review?",
        "end_user_id": "user_42",
        "limit": 3
      }'
  5. An answer carries the answer text and the sources, distinguishing cited evidence from retrieved context. Ask a question:
    curl -X POST https://api.fryri.com/v1/answer \
      -H "Authorization: Bearer $FRYRI_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "question": "When is the Atlas launch review, and what does the project use?",
        "end_user_id": "user_42"
      }'

Complete example

Python, with requests:

import os
import time

import requests

API = "https://api.fryri.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FRYRI_API_KEY']}"}
USER = "user_42"


def call(method, path, **kwargs):
    response = requests.request(method, API + path, headers=HEADERS, **kwargs)
    response.raise_for_status()
    return response.json()


# Store
memory = call("POST", "/memories", json={
    "text": "Project Atlas uses Python and FastAPI. The launch review is on 14 October.",
    "title": "Project Atlas notes",
    "end_user_id": USER,
})

# Wait until ready
while memory["status"] == "processing":
    time.sleep(2)
    memory = call("GET", f"/memories/{memory['id']}", params={"end_user_id": USER})

# Recall
search = call("POST", "/search", json={
    "query": "When is the Atlas launch review?", "end_user_id": USER,
})
answer = call("POST", "/answer", json={
    "question": "When is the Atlas launch review?", "end_user_id": USER,
})

# Read response
print(search["results"][0]["text"])
print(answer["answer"])

TypeScript on Node 18 or later. Save it as quickstart.mts and run npx tsx quickstart.mts:

const API = "https://api.fryri.com/v1";
const USER = "user_42";

async function call(method: string, path: string, body?: object) {
  const response = await fetch(API + path, {
    method,
    headers: {
      Authorization: "Bearer " + process.env.FRYRI_API_KEY,
      "Content-Type": "application/json",
    },
    body: body && JSON.stringify(body),
  });
  if (!response.ok) throw new Error(response.status + " " + (await response.text()));
  return response.json();
}

// Store
let memory = await call("POST", "/memories", {
  text: "Project Atlas uses Python and FastAPI. The launch review is on 14 October.",
  title: "Project Atlas notes",
  end_user_id: USER,
});

// Wait until ready
while (memory.status === "processing") {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  memory = await call("GET", "/memories/" + memory.id + "?end_user_id=" + USER);
}

// Recall
const search = await call("POST", "/search", {
  query: "When is the Atlas launch review?",
  end_user_id: USER,
});
const answer = await call("POST", "/answer", {
  question: "When is the Atlas launch review?",
  end_user_id: USER,
});

// Read response
console.log(search.results[0]?.text);
console.log(answer.answer);

The OpenAPI spec at https://api.fryri.com/v1/openapi.json describes every call on this page, for a client of your own. https://api.fryri.com/v1/docs lets you try each call in the browser.

Authentication

An API key authenticates every request. Create keys in the developer console, which shows each key once. Send the key as a Bearer token:

Authorization: Bearer $FRYRI_API_KEY

A missing, revoked or mistyped key fails with 401 unauthorized. A key works on the library of its own account and on the end users of that account. It's recommended to keep keys on your server, in an environment variable such as FRYRI_API_KEY.

Credit

Calls spend from the account's prepaid credit, separate from any Fryri plan. A newly verified account starts with $1 of credit. Every call needs credit, including the free ones. Without it, a call fails with 402 insufficient_credits. Top-ups, spend caps per key, usage and the request log are in the developer console. Pricing lists what each call costs.

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. Its Retry-After header gives the seconds to wait before the next try. Requests with a missing or invalid key have a separate limit: after 100 of them in one minute from one client, the next requests from that client also fail with 429 until the minute ends.

Request IDs

Every response carries an X-Request-ID header, and an error body repeats it as request_id. It's recommended to log it and quote it when you contact support.

Memories

A memory is one thing your app adds: a note, document or conversation snapshot.

The memory object

Add, list and get return the same memory object:

FieldTypeDescription
idstringThe memory id, such as file_NkolfLrElMmM2fCn.
typestringtext (plain text and Markdown), document (other files), or conversation (ordered turns).
titlestring or nullThe title sent with the add, else the file name. null for text added without a title.
statusstringprocessing until search can find the memory, then ready. failed when it cannot be processed.
status_reasonstring or nullWhy the memory is failed or still processing. See the table below.
created_atstringWhen the memory was added, as an ISO 8601 time in UTC.
source_refstring or nullYour own id for the original, such as a file path, a URL or a record id, as sent with the add. See Replace by reference.
sha256string or nullThe SHA-256 of the content as it was added, in hex: file bytes, UTF-8 text, or canonical JSON for a conversation. Compare it with the hash of your own copy to skip sending unchanged content. null when Fryri holds no hash for the memory.

status and status_reason go together as follows:

statusstatus_reasonMeaning
processingnullFryri is reading and indexing the memory.
processingqueuedThe memory was added in the Fryri app and waits for the daily document allowance of the account, then processes on its own. Memories added through the API never wait.
readynullSearch and answers can find the memory.
failedprocessing_failedReading the memory failed. Adding the same content again starts a new attempt.
failedunsupported_formatFryri cannot read the format of the file.
failedreading_allowance_reachedThe monthly allowance of the account for reading scanned pages is used up.

POST /v1/memories

POST /v1/memories adds a note, document or conversation snapshot. The memory comes back at once with the status processing, and search finds it once it is ready. A short note is usually ready within seconds. A long document, or a large batch added at once, can take a few minutes.

Send exactly one of text, file or messages as JSON, or upload a file as multipart/form-data. A JSON body must carry exactly one of the three. Otherwise, the request fails with 422 invalid_request.

JSON body

ParameterTypeRequiredDefaultDescription
textstringOne of threeText to remember, such as a note, a message or a transcript. Length [1, 200000].
fileobjectOne of threeA document sent inline, with the three fields below. A file over about 8 MB goes as a multipart/form-data upload instead.
messagesarrayOne of threeAn ordered conversation snapshot of 1 to 200 user or assistant turns, as described below.
file.namestringYes, in fileThe file name with its extension, such as contract.pdf. Length [1, 400].
file.datastringYes, in fileThe file bytes, base64-encoded, up to about 8 MB of file.
file.typestringNoThe MIME type, such as application/pdf. Fryri works it out from file.name when it is absent.
titlestringNoA name for the memory. Length [1, 400].
source_refstringNoYour own id for the original, such as a file path, a URL or a record id. Adding again with the same source_ref replaces the earlier memory. See Replace by reference. Length [1, 2000].
end_user_idstringNoAdds the memory to one of your end users, created on first use. Length [1, 256].

messages is a JSON array of 1 to 200 turns. Each turn has role (user or assistant), nonblank content and optional created_at with a time zone. Array order is authoritative. The rendered transcript, including turn headings, must fit within 30,000 characters. Send a complete updated snapshot with the same source_ref to replace an earlier one. See Conversation snapshots.

Multipart form fields

FieldTypeRequiredDefaultDescription
filefileYesThe document to remember, up to 50 MB.
titlestringNoAs in the JSON body.
source_refstringNoAs in the JSON body.
end_user_idstringNoAs in the JSON body.

Header

HeaderTypeRequiredDefaultDescription
Idempotency-KeystringNoMakes a retry safe. The same key and body replay the first response for 24 hours. The same key with a different body fails with 409 idempotency_key_reused.

It's recommended to send an Idempotency-Key with every add, so a retry after a dropped connection never adds the memory twice.

The response is a memory object with two more fields. duplicate is true when identical content was already stored. id is then the existing memory, and nothing new is added. replaced lists the ids of the earlier memories that this add replaced, and it is empty when nothing was replaced. Reading and indexing the content bills its model cost to the prepaid credit of the account: about $0.003 for a short note. A long document costs more, because the cost follows the length of the content.

Request

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. The launch review is on 14 October.",
    "title": "Project Atlas notes",
    "source_ref": "crm:note:881",
    "end_user_id": "user_42"
  }'

Response

{
  "id": "file_NkolfLrElMmM2fCn",
  "type": "text",
  "title": "Project Atlas notes",
  "status": "processing",
  "status_reason": null,
  "created_at": "2026-09-25T09:02:54.915644Z",
  "source_ref": "crm:note:881",
  "sha256": "886d5886b41216cf0dc8e2234f4ffb24cb5666425200e5e2c5ee33d841b91be4",
  "duplicate": false,
  "replaced": []
}

Duplicates

The same request, sent again, returns the existing memory with duplicate set to true.

Response

{
  "id": "file_NkolfLrElMmM2fCn",
  "type": "text",
  "title": "Project Atlas notes",
  "status": "processing",
  "status_reason": null,
  "created_at": "2026-09-25T09:02:54.915644Z",
  "source_ref": "crm:note:881",
  "sha256": "886d5886b41216cf0dc8e2234f4ffb24cb5666425200e5e2c5ee33d841b91be4",
  "duplicate": true,
  "replaced": []
}

Replace by reference

A memory is one copy of one original. An add with a source_ref that earlier memories carry replaces them. The new content gets a new id, the earlier memories stop appearing in search and answers at once, and replaced lists their ids. An add of unchanged content returns the existing memory with duplicate set to true and keeps it. A failed add leaves the earlier memory in place.

Here src/auth.py was added once before and has changed since. Keep a codebase in sync has a complete script for a whole folder or repository.

Request

curl -X POST https://api.fryri.com/v1/memories \
  -H "Authorization: Bearer $FRYRI_API_KEY" \
  -F "file=@src/auth.py" \
  -F "source_ref=myrepo/src/auth.py" \
  -F "end_user_id=user_42"

Response

{
  "id": "file_hqjP2q_DlrHZT-4U",
  "type": "document",
  "title": "auth.py",
  "status": "processing",
  "status_reason": null,
  "created_at": "2026-09-25T09:07:09.917301Z",
  "source_ref": "myrepo/src/auth.py",
  "sha256": "785108e6758610a47355924cf936ce293ac1c6e5e6c7cd9b8752c19c78c00e26",
  "duplicate": false,
  "replaced": [
    "file_kR1jHO7CDRwaoCEw"
  ]
}

Upload a file

A multipart/form-data upload takes a file of up to 50 MB. The file type comes from the content type of the part, or from the file name when that is missing or application/octet-stream.

Request

curl -X POST https://api.fryri.com/v1/memories \
  -H "Authorization: Bearer $FRYRI_API_KEY" \
  -F "file=@atlas-budget.txt" \
  -F "title=Atlas budget" \
  -F "end_user_id=user_42"

Response

{
  "id": "file_khWTQ9FQRD0vNdAx",
  "type": "text",
  "title": "Atlas budget",
  "status": "processing",
  "status_reason": null,
  "created_at": "2026-09-25T09:02:58.286297Z",
  "source_ref": null,
  "sha256": "c6a6c1b533130aa2c02bb580edb9263885ac229f1ebd7016e1a341885a908ee4",
  "duplicate": false,
  "replaced": []
}

Supported files

A file memory holds a document. Fryri reads these files:

KindFiles
DocumentsPDF, Word (.docx), PowerPoint (.pptx), Excel (.xlsx), OpenDocument (.odt, .ods, .odp), RTF and EPUB
Text and dataPlain text, Markdown, CSV, JSON and HTML
Email.eml and .msg
CodeSource code files and Jupyter notebooks

An add of a photo, a video or an audio file, judged by its content type or its file extension, fails with 422 unsupported_format and stores nothing.

Add a web page

To remember a web page, fetch it in your own code and add its text, with the address of the page as source_ref. An add of the same address later replaces the earlier copy.

Request

curl -X POST https://api.fryri.com/v1/memories \
  -H "Authorization: Bearer $FRYRI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Example Domain. This domain is for use in documentation examples.",
    "title": "Example Domain",
    "source_ref": "https://example.com",
    "end_user_id": "user_42"
  }'

GET /v1/memories

GET /v1/memories lists memories, newest first. The list covers every note, document and conversation snapshot in the library, however it arrived. Listing is free.

ParameterTypeRequiredDefaultDescription
limitintegerNo20Memories on this page. Default value is 20. Range [1, 100].
cursorstringNoThe next_cursor from the previous page.
source_ref_prefixstringNoLists only the memories whose source_ref starts with this text, such as myrepo/. Characters like % and _ match themselves. Length [1, 2000].
end_user_idstringNoLists the memories of one of your end users. An id with no memories returns an empty list.

The response holds memories, an array of memory objects, and next_cursor. Pass next_cursor back as cursor for the next page; it is null on the last page. A cursor from a different list starts again at the first page.

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"
    },
    {
      "id": "file_khWTQ9FQRD0vNdAx",
      "type": "text",
      "title": "Atlas budget",
      "status": "ready",
      "status_reason": null,
      "created_at": "2026-09-25T09:02:58.286297Z",
      "source_ref": null,
      "sha256": "c6a6c1b533130aa2c02bb580edb9263885ac229f1ebd7016e1a341885a908ee4"
    }
  ],
  "next_cursor": "eyJwIjp7ImkiOjQ1OSwiayI6..."
}

List one folder or codebase

source_ref_prefix lists the memories you added under one prefix of source_ref, such as the files of one repository.

Request

curl "https://api.fryri.com/v1/memories?end_user_id=user_42&source_ref_prefix=myrepo%2F" \
  -H "Authorization: Bearer $FRYRI_API_KEY"

Response

{
  "memories": [
    {
      "id": "file_hqjP2q_DlrHZT-4U",
      "type": "document",
      "title": "auth.py",
      "status": "processing",
      "status_reason": null,
      "created_at": "2026-09-25T09:07:09.917301Z",
      "source_ref": "myrepo/src/auth.py",
      "sha256": "785108e6758610a47355924cf936ce293ac1c6e5e6c7cd9b8752c19c78c00e26"
    }
  ],
  "next_cursor": null
}

GET /v1/memories/{memory_id}

GET /v1/memories/{memory_id} returns one memory with its status, its text and a link to its original file. Reading is free.

Poll it after an add until status is ready or failed. A short note is usually ready within seconds. A long document can take a few minutes.

ParameterTypeRequiredDefaultDescription
memory_idstringYesThe memory id, in the path.
cursorstringNoThe next_cursor from the previous read of this memory.
end_user_idstringNoThe end user the memory belongs to.

A memory of an end user must be read with its end_user_id. Otherwise, the request fails with 404 not_found. A cursor from another memory, or from an earlier version of its text, fails with 422 invalid_cursor.

The response is a memory object with six more fields:

FieldTypeDescription
summarystring or nullA one-line summary of a document, once it is processed.
textstring or nullThe text of the memory: a note as it was added, or the extracted text of a document.
linesobject or nullFor a document, which lines text holds: start and end (1-based and inclusive) out of total.
next_cursorstring or nullMore text follows. Pass it back as cursor to read the next part.
messagesarray or nullThe original turns of a processed conversation, on its first page only, as described below.
download_urlstring or nullA link to the original file, valid for 5 minutes. null when no original file is available.

A processed conversation also returns messages on its first page: original role, content and created_at, with 1-based turn, line_start and line_end. Other memories and later pages return messages: null.

The text of a long document arrives in parts. lines counts the same lines as location.line_start in a search result.

Request

curl "https://api.fryri.com/v1/memories/file_NkolfLrElMmM2fCn?end_user_id=user_42" \
  -H "Authorization: Bearer $FRYRI_API_KEY"

Response

{
  "id": "file_NkolfLrElMmM2fCn",
  "type": "text",
  "title": "Project Atlas notes",
  "status": "ready",
  "status_reason": null,
  "created_at": "2026-09-25T09:02:54.915644Z",
  "source_ref": "crm:note:881",
  "sha256": "886d5886b41216cf0dc8e2234f4ffb24cb5666425200e5e2c5ee33d841b91be4",
  "summary": "A brief note about Project Atlas, which uses Python and FastAPI, with a launch review scheduled for 14 October.",
  "text": "Project Atlas uses Python and FastAPI. The launch review is on 14 October.",
  "lines": {
    "start": 1,
    "end": 1,
    "total": 1
  },
  "next_cursor": null,
  "download_url": "https://****.r2.cloudfla..."
}

DELETE /v1/memories/{memory_id}

DELETE /v1/memories/{memory_id} deletes one memory. It stops appearing in search and answers at once, together with anything learned from it. Deleting is free.

ParameterTypeRequiredDefaultDescription
memory_idstringYesThe memory id, in the path.
end_user_idstringNoThe end user the memory belongs to.

A memory of an end user must be deleted with its end_user_id. Otherwise, the request fails with 404 not_found. A second delete of the same memory also fails with 404. To erase everything stored for one end user, call DELETE /v1/end-users/{end_user_id}.

Request

curl -X DELETE "https://api.fryri.com/v1/memories/file_khWTQ9FQRD0vNdAx?end_user_id=user_42" \
  -H "Authorization: Bearer $FRYRI_API_KEY"

Response

{
  "id": "file_khWTQ9FQRD0vNdAx",
  "deleted": true
}

POST /v1/answer

POST /v1/answer answers a question from memories and lists cited evidence and retrieved context separately. One call finds what is relevant, reads it and writes the answer. An answer draws only on the memories of the library, and it never adds or changes a memory.

ParameterTypeRequiredDefaultDescription
questionstringYesThe question, in natural language. Length [1, 50000].
end_user_idstringNoAnswers from the memories of one of your end users.
streambooleanNofalseDelivers the answer as server-sent events while it is written. Default value is false.

Response fields:

FieldTypeDescription
answerstringThe answer, as Markdown text.
sources[].typestringtext, document or conversation for a memory, or reference for a quoted passage of a reference work.
sources[].idstring or nullThe memory id, for a memory source.
sources[].titlestring or nullThe title of the memory, or the citation of a reference.
sources[].textstring or nullUp to 300 characters of source text or retrieved context. For a numbered citation, these are the delivered source lines.
sources[].citedbooleanTrue when the answer cites verified, delivered source lines. False means retrieved context.
sources[].citationinteger or nullThe number in the answer's [N] marker. Null for retrieved context.
sources[].locationobject or nullInclusive 1-based line_start and line_end. Conversation citations also have turn_start and turn_end.
sources[].source_refstring or nullYour reference for this source, when supplied on add.
sources[].text_truncatedbooleanTrue when the cited range exceeds the 300-character quote. Read the memory to continue.
cost_usdnumberWhat this call cost, in US dollars.

sources lists numbered citations first, then retrieved context. Read cited to distinguish the two. An answer costs $0.02 and covers up to 32,000 input tokens and 4,000 output tokens. A longer run bills further $0.02 steps. Fryri charges the $0.02 only when the answer completes.

Request

curl -X POST https://api.fryri.com/v1/answer \
  -H "Authorization: Bearer $FRYRI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "When is the Atlas launch review, and what does the project use?",
    "end_user_id": "user_42"
  }'

Response

{
  "answer": "The **launch review is on 14 October**, and Project Atlas uses **Python and FastAPI**.\n\nThat's from your \"Project Atlas notes.txt\". Given today's date, that review is about three weeks away. The notes...",
  "sources": [
    {
      "type": "text",
      "id": "file_NkolfLrElMmM2fCn",
      "title": "Project Atlas notes",
      "text": "Project Atlas uses Python and FastAPI. The launch review is on 14 October."
    }
  ],
  "cost_usd": 0.02
}

Streaming

With stream set to true, the response is text/event-stream. Each event is one data: line that holds a JSON object with a type:

typeFieldsMeaning
statustextWhat the answer is doing right now, such as looking through the library.
deltatextThe next piece of the answer.
resetDiscard the text so far. The answer starts again.
doneanswer, sources, cost_usdThe last event. It carries the same body as a response without stream.
errorerrorThe last event when the answer stops early, with code, message and retryable.

The stream ends with done or error. The answer in done is final, so it replaces the joined delta text. Lines that start with : are heartbeats that keep the connection open. A failure before the first event, such as a missing key, returns the JSON error envelope with its HTTP status.

Request

curl -N -X POST https://api.fryri.com/v1/answer \
  -H "Authorization: Bearer $FRYRI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "When is the Atlas launch review?",
    "end_user_id": "user_42",
    "stream": true
  }'

Response

data: {"type": "delta", "text": "The Atlas launch review is on **14 October** (per your Project Atlas notes, so 14 October 2026, just under three weeks away)."}

data: {"type": "done", "answer": "The Atlas launch review is on **14 October** (per your Project Atlas notes, so 14 October 2026, just under three weeks away).", "sources": [{"type": "text", "id": "file_NkolfLrElMmM2fCn", "title": "Project Atlas notes", "text": "Project Atlas uses Python and FastAPI. The launch review is on 14 October."}], "cost_usd": 0.02}

End users

end_user_id scopes a call to one of your own users. Each end user has a private library. Calls with the same id only ever reach that library, and calls without end_user_id use the library of your own account.

The id is any stable id you already use for the user, [1, 256] printable characters. Fryri keeps it exactly as sent, so user_42 and User_42 are two different end users. POST /v1/memories, POST /v1/search and POST /v1/answer create the end user the first time they use its id. Reads and deletes never create one, so a mistyped id reads as an empty library.

The ids belong to your account, so every key of the account reaches the same end users. Spend always lands on your account. The developer console lists your end users and what each one spent.

DELETE /v1/end-users/{end_user_id}

DELETE /v1/end-users/{end_user_id} permanently deletes one end user and everything stored for them: memories, their original files, and everything learned from them. It takes effect at once and cannot be undone. Your own memories and your other end users stay as they are.

Erasing the same id again fails with 404 not_found, and the next add, search or answer with the same id starts a new, empty library. It's recommended to call it from your own account-deletion flow. Deleting is free.

ParameterTypeRequiredDefaultDescription
end_user_idstringYesThe id you passed as end_user_id, in the path. Length [1, 256]. An id with a slash goes in percent-encoded, such as team%2F42.

Request

curl -X DELETE "https://api.fryri.com/v1/end-users/user_42" \
  -H "Authorization: Bearer $FRYRI_API_KEY"

Response

{
  "deleted": true,
  "end_user_id": "user_42"
}

Pricing

Every call spends from the prepaid credit of its account at these prices. Search and answer responses report their cost in cost_usd.

CallPrice
POST /v1/memoriesThe model cost of reading and indexing the content: about $0.003 for a short note. A long document costs more.
POST /v1/search$0.0025 per call.
POST /v1/answer$0.02 per answer. One answer covers up to 32,000 input tokens and 4,000 output tokens; a longer run bills further $0.02 steps.
GET /v1/memories, GET /v1/memories/{memory_id}, DELETE /v1/memories/{memory_id}, DELETE /v1/end-users/{end_user_id}Free.

A newly verified account starts with $1 of credit. Every call needs credit, including the free ones. Top-ups, spend caps per key, usage and the request log are in the developer console.

Errors

Every error returns one JSON envelope with its HTTP status. error.code is a stable machine code to branch on, and error.message is prose that may change. error.retryable says whether the same call, sent again unchanged, can succeed. It's recommended to read it instead of inferring from the status. request_id matches the X-Request-ID header.

Some errors carry extra fields: use on a 410, errors (every field problem) on a 422, and key_monthly_budget_usd with key_spend_this_month_usd on key_budget_reached.

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.
409memory_format_conflictfalseIdentical conversation bytes already exist in another format. Remove that source explicitly before importing them as a conversation.
410endpoint_retiredfalseCall the endpoint in error.use. See Retired endpoints.
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 5xx error carries the same envelope, with retryable set to true.

Missing key

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

Response 401

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

Unknown memory id

curl "https://api.fryri.com/v1/memories/file_AAAAAAAAAAAAAAAA" \
  -H "Authorization: Bearer $FRYRI_API_KEY"

Response 404

{
  "error": {
    "code": "not_found",
    "message": "No memory with that id in this library.",
    "retryable": false
  },
  "request_id": "b8bc43a847964c60a8c05612506be894"
}

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 422

{
  "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"
}

Retired endpoints

On 25 September 2026 the API moved to the seven calls on this page. A path from the earlier API fails with 410 endpoint_retired, and error.use names the call to make instead, when one exists.

Old pathNowCall instead
POST /v1/capture410POST /v1/memories. file.data, file.name and file.type replace content_base64, filename and content_type.
POST /v1/capture/batch410POST /v1/memories, once per item.
POST /v1/chat410POST /v1/answer. The answer text moves from reply to answer.
GET /v1/search405POST /v1/search, with query in the JSON body in place of q. Results carry type and text in place of kind and snippet.
/v1/documents410GET /v1/memories and GET /v1/memories/{memory_id}.
/v1/library/export410GET /v1/memories.
/v1/grep, /v1/graph, /v1/entities410POST /v1/search.
/v1/facts410POST /v1/memories to add, POST /v1/search to find.
GET /v1/end-users410The developer console shows the list of end users. DELETE /v1/end-users/{end_user_id} stays.
/v1/usage, /v1/runs, /v1/wallet410The developer console.
/v1/webhooks410No replacement. Read the memory with GET /v1/memories/{memory_id} until status is ready.
/v1/tools, /v1/byo-key, /v1/storage, /v1/research, /v1/conversations, /v1/calendar, /v1/evolving410No replacement. These left the public API.

Retired path

curl -X POST https://api.fryri.com/v1/capture \
  -H "Authorization: Bearer $FRYRI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "x"
  }'

Response 410

{
  "error": {
    "code": "endpoint_retired",
    "message": "`POST /v1/capture` was retired on 2026-09-25. Use POST /v1/memories.",
    "retryable": false,
    "use": "POST /v1/memories"
  },
  "request_id": "f13dd44eafa741f4953f93df4e9410a4"
}

Old search method

curl "https://api.fryri.com/v1/search" \
  -H "Authorization: Bearer $FRYRI_API_KEY"

Response 405

{
  "error": {
    "code": "method_not_allowed",
    "message": "`GET /v1/search` is not supported. Use POST.",
    "retryable": false
  },
  "request_id": "913f828f5e7d4c8699222e09845a89be"
}

Removed on 26 September 2026

On 26 September 2026 the API narrowed to text and documents. A request that still uses one of these parts fails as follows:

RemovedNowInstead
url on POST /v1/memories422 invalid_requestFetch the page in your own code and add its text, with its address as source_ref. See Add a web page.
Photos, video and audio on POST /v1/memories422 unsupported_formatAdd text or a document. See Supported files.
web_search on POST /v1/answer422 invalid_requestLeave the field out. An answer draws only on memories.
The capture.completed and capture.failed webhooksNo deliveriesRead the memory with GET /v1/memories/{memory_id} until status is ready or failed.
POST /mcp410 endpoint_retiredCall the /v1 routes over HTTP.
The Python and TypeScript client filesNo longer servedCall the API over HTTP, as the examples on this page do.