POST /v1/capture adds material to the library; three read endpoints get it back. There are three ways to send a document, and you pick per call by what you include.
Three ways to capture#
Text push. Extract text yourself and send only that. The file's bytes stay on your side.
Request
curl -X POST https://api.fryri.com/v1/capture \
-H "Authorization: Bearer $FRYRI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "MASTER SERVICE AGREEMENT...", "title": "Acme MSA 2026", "source_ref": "s3://acme-docs/msa-2026.pdf"}'Response
{
"ok": true,
"status": "created",
"dedup": false,
"detail": "Captured.",
"id": "file_4DKRDkce4MDwHZvG",
"file_id": "file_4DKRDkce4MDwHZvG",
"reject_code": null,
"retryable": null,
"source_ref": "s3://acme-docs/msa-2026.pdf"
}title names the document in the library. source_ref is an opaque pointer back to your original (a URL, object key or your own id). It is stored verbatim and returned in the capture receipt and document list, so you can map a document back to your file.
Bytes push. Send the file itself as content_base64 with filename and content_type. Fryri stores it, extracts text (including OCR for scans), and files it.
Your own bucket. Register your S3 or R2 bucket once and the raw bytes of every file captured with that key are stored in your bucket instead of Fryri's. Fryri still receives each file to extract and index it; the searchable library stays on Fryri. Connect with bucket keys, or on AWS with an IAM role so no long-lived secret leaves your account. See the reference for the bucket endpoints.
Every capture returns a status: created, deduped (an identical payload already existed, the returned id points at it), or rejected (with a reason and whether a retry can help).
Capture a local folder#
There is no folder connector to configure. Your files are on your machine, so a short loop pushes them: walk the folder, send the bytes. POST /v1/capture/batch takes up to 20 items per call. Check each response and handle rate-limit errors in your client. A call with more than 20 items fails with 422 invalid_request.
Complete example
import base64, mimetypes, os, requests
from pathlib import Path
API = "https://api.fryri.com/v1/capture/batch"
HEADERS = {"Authorization": f"Bearer {os.environ['FRYRI_API_KEY']}"}
# Read files
items = [{
"content_base64": base64.b64encode(p.read_bytes()).decode(),
"filename": p.name,
"content_type": mimetypes.guess_type(p.name)[0] or "application/octet-stream",
"source_ref": str(p),
} for p in Path("./contracts").rglob("*") if p.is_file()]
# Send request
for i in range(0, len(items), 20):
requests.post(API, headers=HEADERS, json={"items": items[i:i+20]}).raise_for_status()Response
{
"total": 2,
"succeeded": 2,
"results": [
{
"index": 0,
"ok": true,
"status": "created",
"dedup": false,
"detail": "Captured.",
"id": "file_UOcg7HBUApU0Z58y",
"file_id": "file_UOcg7HBUApU0Z58y",
"reject_code": null,
"retryable": null,
"source_ref": "contracts/acme-sow.txt"
},
...
]
}Re-running the loop is safe: a file that hasn't changed comes back deduped and nothing is written twice, so the same loop doubles as a crude sync. Use GET /v1/documents to retrieve each document's source_ref and map it to the local path.
Getting it back#
Request
curl "https://api.fryri.com/v1/search?q=Acme+termination+notice+period" \
-H "Authorization: Bearer $FRYRI_API_KEY"Response
{
"query": "Acme termination notice period",
"count": 10,
"results": [
{
"id": "file_S-eoJdETKIz7doTE",
"kind": "file_excerpt",
"snippet": "r base rate)\n- Limitation of liability (cap at fee amount)\n- Termination: 14-30 days written notice\n- Dispute resolution: mediation before litigation\n- Governing law: New Zealand ...",
"score": 0.378451173685076,
"from_keyword": true,
"content_date": "2026-08-19T17:44:28.856532"
},
...
],
"has_more": true,
"next_cursor": "eyJvIjoxMCwicyI6ImJiMmEwMTJkYWMxYWYyNzkifQ...."
}GET /v1/search matches by meaning. A result's kind says what matched: file_excerpt is a document in the library, image a photo, chat a past message, and creation a document, chart, table or app Fryri made in chat (its id is the creation's item id). limit sets the page size. Default value is 10, range [1, 50]. next_cursor is opaque; pass it back as cursor for the next page. GET /v1/grep does literal pattern matching when you know the exact string. POST /v1/chat answers a question grounded in the library and cites which documents the answer came from.