RAGfly — Public REST API v1
/v1 is the stable English HTTP face of RAGfly for agents, developers and
automation platforms. It wraps the authenticated internal REST routes at the
edge. Internal routes remain Spanish for Web/Desktop and are not part of this
public contract: an API key that calls one gets 403.
Base URL: https://api.ragfly.ai
OpenAPI: https://api.ragfly.ai/openapi.json
Swagger: https://api.ragfly.ai/docs
This page is the short reference. The exhaustive one —every route with its request and response schema— is the OpenAPI document above.
There is no /v2. A future incompatible contract would be introduced explicitly
as a new version; /v1 is the current public contract.
Authentication
Every /v1 route requires:
Authorization: Bearer <JWT-or-rf_API_key>
- API key (
rf_...): what integrations use. Create and revoke it in the RAGfly web app. It operates only/v1(MCP, the SDKs and the CLI go through it), with its owner's RBAC — the key's role, filtered by the owner's access level, decides which actions; the owner's group, entity and area decide which data. - JWT: an optional person's web session. A person can use it with
/v1, but key management belongs in the web app. The web application's/auth/*routes are not part of the public integration contract.
The public contract ignores Accept-Language: field names, catalog codes, enum
values, published schemas/defaults, validation details and standard success or
error messages are always English. This applies to nested JSON too. User
document content and other tenant-authored text keep their original language.
The session's locale, when present, is a language preference tag; it does not
localize the API protocol.
Entity focus
An API key has either a fixed entity or a flexible entity focus. A fixed key
cannot change its entity. A flexible key may select an authorized entity or
release focus by sending {"entity_code":null} to
POST /v1/session/active-entity. The request must include entity_code;
null is the explicit release value. RAGfly stores the resulting focus for
that key, so it remains after the client process is reconstructed. The key
cannot select an entity outside its owner's authorized scope.
An area is an organizational access focus inside an entity; a location
is a document folder used as a temporary filter for one request. An area focus
is persistent for an API key and can be released without releasing the entity.
Selecting an area selects its entity as well. location_code only filters the
current request and its folder descendants; it never changes the session.
GET /v1/session includes active_area, effective_area,
authorized_area_root, and area_focus_is_explicit. Use
POST /v1/session/active-area with {"area_code":"FINANCE"} to set an area
or {"area_code":null} to release it. GET /v1/areas and
GET /v1/locations return visible, paginated trees; both accept entity_code,
parent_code, query, limit (1–200, default 50), and cursor.
Pass location_code to GET /v1/documents, POST /v1/documents/search, or
POST /v1/ask to narrow that request to a visible folder and its descendants.
An invisible or unknown location returns 404 NOT_FOUND.
If no entity can be resolved for an area listing, the response is
400 INVALID_CONTEXT. If a saved area focus or fixed key area is no longer
inside the owner's current permission, the response is 409 CONTEXT_CONFLICT.
Release or replace a stale focus; if the key's fixed area root itself lost
permission, issue a newly scoped key before requesting documents again.
Routes
| Method | Route | Purpose |
|---|---|---|
GET |
/v1/session |
Authenticated identity and active context |
POST |
/v1/session/active-entity |
Set {"entity_code":"000057"} or release with {"entity_code":null} |
POST |
/v1/session/active-area |
Set {"area_code":"FINANCE"} or release with {"area_code":null} |
GET |
/v1/areas |
Paginated organizational areas visible in the current context |
GET |
/v1/locations |
Paginated document folders visible in the current context |
GET |
/v1/operations |
Operations this key can run (RBAC-filtered) |
GET |
/v1/operations/{code} |
One operation with its input_schema and output_schema |
POST |
/v1/operations/{code}:execute |
Run an operation: {"input": {...}, "confirm": false} |
GET |
/v1/documents |
Paginated corpus documents (status, limit, page, location_code) |
GET |
/v1/documents/{document_code} |
Document detail |
GET |
/v1/documents/{document_code}/edges |
Document graph edges (neighbor_limit) |
POST |
/v1/documents/search |
Hybrid search: vector + lexical, fused with RRF (location_code?) |
GET |
/v1/spaces |
List workspaces |
GET |
/v1/spaces/{space_id} |
Workspace and its documents (document_limit) |
POST |
/v1/spaces/{space_id}/refresh |
Re-materialize a workspace |
POST |
/v1/spaces/{space_id}/promote |
Promote an AREA to a SPACE |
POST |
/v1/spaces/compose |
Set operation over two workspaces |
POST |
/v1/spaces/{space_id}/read |
Read a workspace (count, manifest, chunks, text) |
GET |
/v1/queue |
Processing queue (process, status, limit) |
GET |
/v1/runs |
Skill run history |
GET |
/v1/catalog |
RBAC-filtered functions and skills (type) |
GET |
/v1/functions/{function_code} |
Function detail: documentation and behaviours |
GET |
/v1/skills |
Skills available to you (the same list as /v1/catalog) |
GET |
/v1/skills/{skill_code} |
Skill detail. Prompt and model come only if your role administers skills |
POST |
/v1/skills/{skill_code}/run |
Queue a skill (space_id or document_code) |
POST |
/v1/ask |
Complete RAG answer (location_code?, mode?, one request only) |
GET |
/v1/agent/context |
Layered prompt, identity, tools and limits |
POST |
/v1/agent/tools/{public_name} |
Run one tool listed by /v1/agent/context |
GET |
/v1/organization |
This tenant's profile: description and system prompt |
PUT |
/v1/organization |
Write the profile (group and/or entity) |
POST |
/v1/organization/draft |
Propose the four profile texts. Does not save |
GET |
/v1/usage |
Plan quotas against what is already consumed |
GET |
/v1/conversations |
Conversation history (function_code, limit) |
DELETE |
/v1/conversations/{conversation_id} |
Delete a conversation and its messages |
GET |
/v1/processes |
Process instances: support, requests, workspace jobs |
GET |
/v1/processes/{process_code} |
One process in full |
PATCH |
/v1/processes/{process_code} |
Update status, priority, title, description, comments, assignee, dates or cost |
Every route applies the key's RBAC. A route the key's role does not reach answers
403 FORBIDDEN; a document outside its group, entity or area answers 404, the
same as one that does not exist.
Discover what a key can do — /v1/operations
/v1/operations is how an integration asks what its key can do. It publishes the
operations behind the screens of the RAGfly app, filtered by the key's RBAC with
the same rule the web app applies: an operation that is not listed does not
exist for this key.
curl https://api.ragfly.ai/v1/operations -H "Authorization: Bearer $RAGFLY_API_KEY"
{
"operations": [
{"code": "documents.get", "kind": "read", "confirm_required": false, "functions": ["DOCUMENTS"]},
{"code": "spaces.delete", "kind": "write_confirm", "confirm_required": true, "functions": ["WORKSPACES"]}
],
"total": 18
}
kind is read, write or write_confirm. functions names the screens of
the web app that use the operation. OPERATIONS.md lists every
operation that exists, with the profile it needs, the same calls on MCP, CLI and
both SDKs, and what is not published and why.
The detail adds the schemas. input_schema is one flat JSON object with the
path, query and body fields together:
curl https://api.ragfly.ai/v1/operations/documents.get -H "Authorization: Bearer $RAGFLY_API_KEY"
{
"code": "documents.get",
"kind": "read",
"confirm_required": false,
"functions": ["DOCUMENTS"],
"input_schema": {
"type": "object",
"properties": {"document_code": {"type": "string"}},
"additionalProperties": false,
"required": ["document_code"]
},
"output_schema": {"properties": {"document_code": {"type": "string"}, "...": {}}}
}
Run it with POST /v1/operations/{code}:execute:
curl -X POST "https://api.ragfly.ai/v1/operations/documents.get:execute" \
-H "Authorization: Bearer $RAGFLY_API_KEY" -H "Content-Type: application/json" \
-d '{"input": {"document_code": "32282"}, "confirm": false}'
# → {"code": "documents.get", "kind": "read", "executed": true, "result": {...}}
A write_confirm operation (deletes, reverts, resets) does nothing unless
confirm is true. Without it the answer is a preview, and nothing runs:
{
"code": "documents.revert",
"kind": "write_confirm",
"executed": false,
"confirm_required": true,
"preview": {"input": {"document_code": "...", "source_statuses": ["..."], "target_status": "..."}}
}
Show the preview to the person and repeat the call with "confirm": true only
after they agree. write and write_confirm runs are audited.
| Answer | Meaning |
|---|---|
404 NOT_FOUND |
The code does not exist or is not visible to this key. The two cases are not told apart |
422 VALIDATION_ERROR with details.unknown_field_count |
The input carries fields the schema does not declare; their names are not echoed |
422 VALIDATION_ERROR with details.missing_fields |
A required field is missing |
403 FORBIDDEN |
The key's RBAC does not reach the concrete route |
Field names, catalog codes, enum values, defaults, validation details and
messages written by the API in input, schemas, errors and result are
English. Catalog codes use the English alias
stored for their catalog row; for example, document status is VECTORIZED.
document_statuses.list is the source for the current allowed values. The API
never returns the internal database code. Tenant-authored names, descriptions,
prompts and document content keep the language in which they were written; they
are content, not protocol messages.
Set up your organization first
Two texts per level —group and entity— decide how well RAGfly serves you: description is prose
about who you are, and system_prompt is the instruction the model follows when answering about
your documents. The system prompt is injected into every skill with organization scope, which
is the whole ingestion pipeline and not only chat, so a tenant that leaves them empty ingests and
answers worse than one that filled them in. Do it before your first load.
These routes need a key whose role can manage the organization profile, normally
a group administrator's; a key without profile-management access gets 403 on the read and
on the draft.
# What is missing?
curl https://api.ragfly.ai/v1/organization -H "Authorization: Bearer $RAGFLY_API_KEY"
# → { "group": {...}, "entity": {...}, "missing": ["group.description", ...] }
# Propose from a source text you bring (your "about us" page, for instance).
# RAGfly does not fetch URLs: read the page yourself and pass the text.
curl -X POST https://api.ragfly.ai/v1/organization/draft \
-H "Authorization: Bearer $RAGFLY_API_KEY" -H "Content-Type: application/json" \
-d '{"source_text":"We are a structural engineering consultancy..."}'
# Review, then write. Only the fields you send are written.
curl -X PUT https://api.ragfly.ai/v1/organization \
-H "Authorization: Bearer $RAGFLY_API_KEY" -H "Content-Type: application/json" \
-d '{"group_description":"...","group_system_prompt":"..."}'
Know what you have left
curl https://api.ragfly.ai/v1/usage -H "Authorization: Bearer $RAGFLY_API_KEY"
{
"plan_code": "Growth", "period_start": "2026-09-05", "period_end": "2026-10-05",
"quotas": [
{ "feature": "ACTIVE_CORPUS", "unit": "pages", "included": 5000, "used": 1508,
"on_limit": "BLOCK", "by_entity": [{ "entity_code": "000024", "used": 1508 }] }
]
}
included: null means unlimited. Check it before a large ingestion or a batch of retrievals.
Search example
curl https://api.ragfly.ai/v1/documents/search \
-H 'Authorization: Bearer rf_xxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"query":"active maintenance contracts","limit":5,"min_similarity":0.35}'
Body: query (required, non-empty), limit (1–100, default 10: the maximum
number of documents), min_similarity (0–1, default 0) and entity_code
(optional, one of your own entities).
The search is hybrid (semantic + keyword). With min_similarity above 0, a document
is returned only if its best chunk reaches that similarity; keyword-only matches carry
no similarity and are dropped. url opens the document in the RAGfly web app (the user
needs a session there); for a public web source it is the source's own URL.
Response fields are English:
{
"documents": [{
"code": "32434",
"name": "Maintenance contract 2024.pdf",
"summary": "Maintenance contract between ...",
"location": "/Contracts/2024/Maintenance contract 2024.pdf",
"url": "https://app.ragfly.ai/documents?codigo=32434&pagina=21",
"rrf_score": 0.0325,
"max_similarity": 0.5497,
"rerank_score": null,
"chunks": [
{"text": "...", "page": 21, "extra": {"chunk_number": 26, "similarity": 0.5497}},
{"text": "...", "page": 22, "extra": {"chunk_number": 28, "similarity": 0.544}}
],
"fs": {"home_var": "RAGFLY_HOME_442681", "relative_path": "Contracts/2024/Maintenance contract 2024.pdf", "path": "/Contracts/2024/Maintenance contract 2024.pdf", "origin": "WEB", "is_absolute": false, "is_public_url": false, "is_cloud_only": false}
}],
"total_documents": 1,
"total_chunks": 2,
"duration_ms": 2875
}
- Each document carries its most relevant
chunks.pageisnullwhen the source has no pages. - The chunk's own score is
extra.similarity. Per document:rrf_score(the hybrid rank),max_similarity(its best vector match;nullwhen none of its chunks has a vector score) andrerank_score. - This route is simple retrieval: it does not rerank, so
rerank_scorecomes backnull. locationandfs(abridged above) say where the original lives, by design; see File locations.- Each call counts against the
RETRIEVALSquota (GET /v1/usage).
Ask example
curl https://api.ragfly.ai/v1/ask \
-H 'Authorization: Bearer rf_xxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"question":"What is the renewal date?","function_code":"CHAT-USER"}'
{"answer":"The renewal date is 30 June.","conversation_id":512,"message_id":514,"user_message_id":513}
answer and conversation_id are the stable fields of this response. Pass
conversation_id to continue the same thread. The endpoint answers once the whole
answer is ready, as JSON; it does not stream, and neither do the SDKs.
Help mode
mode is optional. "default" answers from your documents. "help" answers
questions about RAGfly itself — how to use it, how to connect an agent, which
operation does something, how to pin or release the entity focus of a key — written
for a program: it names screens instead of linking to them and points to the exact
routes, tools and operations of this guide.
curl https://api.ragfly.ai/v1/ask \
-H 'Authorization: Bearer rf_xxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"question":"How do I release the entity focus of my key?","mode":"help"}'
Help mode does not search your documents: for that, call /v1/ask without mode.
When the question cannot be settled by answering, it can register a support request.
It answers 409 if help is not enabled for your group and 400 for any other
mode value.
Agent context and agent tools
GET /v1/agent/context returns what an agent needs to reason like the RAGfly
chat for this identity: the layered system_prompt with its hashes, identity,
limits and tools, the list of tools that POST /v1/agent/tools/{public_name}
runs.
Agent tools have stable English public names. Names backed by catalog entries
derive from the catalog's English aliases (codigo_habilidad_en or
codigo_funcion_en); fixed tools use explicit English names. The context only
lists tools allowed for the authenticated identity and selected profile, so
read its tools and input_schema at run time. Pass public_name unchanged to
POST /v1/agent/tools/{public_name}; internal chat tool names are not part of
the public contract. For stable REST operations, use the /v1 routes above
and /v1/operations.
Function detail
/v1/catalog enumerates what the caller can reach; /v1/functions/{function_code}
returns one of them in full, so an agent can learn what a RAGfly screen actually
does before deciding whether it needs it.
curl https://api.ragfly.ai/v1/functions/PROCESS_PIPELINE \
-H 'Authorization: Bearer rf_xxxxxxxxxx'
{
"code": "PROCESS_PIPELINE",
"name": "Alimentación Documentos",
"alias": "Alimentación",
"description": "Long-form description of what the function does end to end.",
"summary": "One line written for an agent.",
"url": "/process-pipeline",
"documentation": "# Alimentación Documentos\n\n## Descripción\n…",
"behaviors": [
{
"class": "STOP",
"section": "Paso 2 — Detener el pipeline en curso",
"text": "Al pulsar el botón de detener durante una ejecución, el pipeline se interrumpe…"
}
],
"operations": [
{"code": "documents.count_by_status", "kind": "read"},
{"code": "ingestion_runs.cancel", "kind": "write_confirm"}
]
}
documentation is the compiled Markdown of the function — the same text the
in-product help shows. behaviors is that documentation in structured form: one
entry per documented behaviour of the screen, each carrying a class.
class |
What it describes |
|---|---|
NAVIGATION |
What loads on entry, or where the screen takes you |
INTERACTION |
A gesture the user performs, and its effect |
BACKGROUND |
What keeps happening without anyone acting |
STOP |
How work in progress is interrupted |
RECOVERY |
How an interrupted state is picked up again |
Behaviours are descriptions, not operations. They tell an agent what a
gesture does; they are not a way to perform it. operations names the
operations the screen uses; whether this key can run one is what
GET /v1/operations answers. To act on the corpus, use the routes above.
Field names, class values and error codes are English like the rest of /v1.
The descriptive name, description, section and text are product content
and come back in the language they were authored in — today Spanish — the same
way document content keeps its own language.
A 404 means the function is outside the caller's catalog. It is the same RBAC
that filters /v1/catalog: the contract is uniform, the visible surface is not.
Errors
All public errors use one English envelope. request_id is echoed when supplied:
{
"code": "NOT_FOUND",
"message": "The requested resource was not found.",
"details": {},
"request_id": "req-123"
}
Known codes include INVALID_REQUEST, INVALID_CONTEXT (an active entity or an
entity_code is required), UNAUTHORIZED, QUOTA_EXCEEDED (the plan quota for
the operation is used up), FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, CONFLICT,
CONTEXT_CONFLICT (a saved area scope no longer matches current permissions),
RATE_LIMITED and INTERNAL_ERROR.
A catalog value without an English public mapping fails closed with
PUBLIC_CODE_MAPPING_MISSING; it is never emitted as an internal Spanish code.
File locations (fs)
Document responses can include these filesystem hints:
{
"home_var": "RAGFLY_HOME_442681",
"relative_path": "MyDocuments/contract.pdf",
"path": "/MyDocuments/contract.pdf",
"origin": "WEB",
"is_absolute": false,
"is_public_url": false,
"is_cloud_only": false
}
Resolve in this order: if is_cloud_only is true, the original remains with
its provider and the logical path is not local; if is_public_url is true or
origin is PUBLIC, open the URL directly; if is_absolute is true, open
path directly; otherwise read the environment variable named by home_var
and join its value with relative_path. The home_var name is per-root, so
documents may refer to different variables.
If home_var is null, empty, or unset, there is no local root available for
that document. Do not guess a root or construct a path from path. For
cloud-only documents, source_id and source_url may be present; provider
access requires the integrator's own credentials.
See MCP.md: Opening a document on disk for examples with multiple roots and cloud-provider details.
What /v1 does not return
The full text of a document and internal machinery. An agent gets a document's
summary, its relevant chunks and its location (location and fs, by design);
never the complete extracted text, and never the functions, skills, processes
and routes the system uses to operate itself.
Internal REST
Routes such as /documentos, /espacios-trabajo and /interfaz are internal
implementation routes for Web/Desktop. Do not build external integrations on
them; use /v1. An API key on an internal route gets 403 with "An API key can
only operate through the public /v1 API".
/v1 is not just a translation of those routes. The catalog it publishes
(/v1/catalog, /v1/skills, /v1/functions/{code}, /v1/operations) lists
only what is meant for integrators: capabilities the system uses to operate
itself are withheld, and calling one by name returns 404 rather than running
it. The internal routes have no such boundary, so an integration built on them
would depend on machinery that is not part of the public contract and can change
without a version bump.
