API conventions
The Foundation4 REST API follows one set of conventions for routes, authentication, request bodies, errors, pagination, filters, identifiers and streaming. This page describes those conventions, which apply to every operation in the API reference. Integrators read this page before writing a client application or a client library.
Base URL and routes
The API server serves every route at the root of the base URL, with no path prefix and no version prefix, for example $FOUNDATION4_URL/pipelines and $FOUNDATION4_URL/pipelines/{id}/search. Two groups of routes are nested:
- OpenAI-compatible endpoint.
POST /openai/v1/chat/completions, described in LLMs. - Model Context Protocol (MCP). The MCP server is served under
/mcp, as described in MCP server.
The API uses four HTTP methods: GET to list and read, POST to create objects and to run actions such as a search, PATCH to change an object and DELETE to remove an object. No route uses PUT.
| Operation | Success status | Body |
|---|---|---|
| Create an object | 201 | The new object |
| Read, list or change an object, or run an action | 200 | The object, the list or the result |
| Delete an object | 204 | None |
The following operations differ from the table: POST /login returns 201 although no object is created, a document expiry request (DELETE with expire=true) returns 200, and POST /api-keys/{id}/permissions and POST /pipelines/{id}/classifications return 200 with an empty body.
Authentication
Every request carries the API key identifier in the x-api-key header and the secret in the x-api-key-secret header. Authenticate describes the headers and the authentication errors, and Access control describes the permissions that each operation requires.
The following routes do not require an API key: the root route /, the health check /healthz, the monitoring routes /metrics and /statistics, and the API documentation routes. Security hardening describes how to restrict these routes at the ingress.
Some operations read additional headers:
| Header | Operations |
|---|---|
x-pipeline-id | Agent execution and the agent retrieval dry run |
x-llm-id | Agent execution and the OpenAI-compatible endpoint |
Header names are not case-sensitive.
Request bodies
Request bodies are JSON documents sent with the header Content-Type: application/json. The following rules apply:
- Unknown fields. Foundation4 ignores fields that an operation does not define. A misspelled optional field therefore has no effect and returns no error. Client applications check the response of a create request for the intended settings. The
parametersobject of an embedding model is the exception: a parameter that the provider does not define returns HTTP 400 with the messageInvalid parameter, as described in Errors. - Unknown query parameters. Foundation4 ignores query parameters that an operation does not define, including misspelled filter names.
- Parsing errors. A request without the JSON content type returns HTTP 415, a body that is not valid JSON returns HTTP 400, and a body with a missing field or a field of the wrong type returns HTTP 422. These three responses have a plain-text body rather than the JSON error body.
Request identifiers and cross-origin requests
Each response from an API operation carries an x-request-id header. The API server generates a universally unique identifier (UUID) for each request, or keeps the value that the client sends in an x-request-id request header. The server records the value in the server's traces. Support requests quote the x-request-id value. Responses from the root route, the health check, the metrics route and the API documentation routes carry no request identifier and no cross-origin headers.
The API server accepts cross-origin requests from any origin. Deployments that restrict browser access to the API do so at the ingress.
Errors
An error from an API operation returns a JSON body with a message field and, for some errors, a details field:
{
"error": {
"message": "Invalid metadata",
"details": {"product": "123 is not of type \"string\""}
}
}
The status code and message identify the error; the API has no separate error code field. The content of details depends on the error. Errors raised by the web framework before an operation runs, such as parsing errors, header errors and requests for unknown paths, return plain text or an empty body instead. The following table lists the status codes and common messages, and Errors lists every message and the cause of the message:
| Status | Meaning | Common messages |
|---|---|---|
| 400 | The request is invalid | Invalid parameter, Invalid metadata, Schema error, Pagination error, Invalid order_by parameter, Classification mismatch, Invalid search type or parameters, Vector embedding not found |
| 401 | Authentication failed | Invalid API Key or Secret, Invalid key ID, Unauthorized: No API Key specified., Unauthorized: No API Secret Key specified. |
| 403 | The key lacks a permission, or the license does not permit the operation | Unauthorized: access level ... required for ..., Invalid license: ..., License limits exceeded for ... |
| 404 | The object does not exist, or the key cannot read the object | Pipeline not found, Document not found, Classification not found, LLM with id ... not found |
| 409 | A unique name is already in use, or another object still references the object | Conflict unique error, Conflict related error |
| 500 | The server or a dependency failed | Internal error, Stream error: ... |
| 501 | The operation or mode is not implemented | Full text search is not enabled for this pipeline, Hybrid placeholder is not implemented yet, History placeholder is not implemented yet, Not implemented |
Schema error and Pagination error carry no details. A Schema error on a search or list request usually means that a filter references a metadata field that the pipeline schema does not declare. A Pagination error means that the request combines offset and cursor parameters, combines first with last, or carries an invalid cursor.
Most requests for a single object that the key cannot read return HTTP 404, the same status as for an object that does not exist. The document operations of a pipeline return HTTP 403 when the pipeline exists and the key lacks the permission.
Pagination
List operations return a page of results in the following envelope:
{
"data": [],
"page_info": {
"has_next": false,
"has_prev": false,
"order_by": null
},
"query_info": {}
}
data holds the objects, page_info describes the page and query_info repeats the filters that the server recognized. A filter that is missing from query_info was not applied. Search results, the versions of a document and fragments requested by identifier return a plain JSON array instead of the envelope. The fragment list of a document version uses the envelope.
Lists support two pagination modes, selected by the query parameters of the request:
| Mode | Parameters | Use |
|---|---|---|
| Cursor | first and after to page forward; last and before to page backward | Stable paging through lists that change during the read |
| Offset | limit and offset | Direct access to a page by position |
The following rules apply to both modes:
- Default mode. A request without pagination parameters uses cursor mode with a page size of 25.
- Page size. The default page size is 25 in both modes. Client applications request pages of 100 objects or fewer.
- Total count.
count=trueaddspage_info.count, the total number of matching objects, at the cost of an additional database query. - Exclusive modes. A request that combines cursor and offset parameters, or
firstandlast, returns HTTP 400 with the messagePagination error.
In cursor mode, page_info carries the cursors before (of the first object on the page) and after (of the last object) and the flag has_next. The next page is requested with after set to page_info.after, and the previous page with last and before set to page_info.before. Cursors are opaque strings. has_prev is true whenever a request uses after on a non-empty list, so a client that pages backward keeps the before cursor of the pages that the client has read.
A cursor records the values of the order_by fields only. When a list is ordered by a field whose values repeat, such as classification, cursor paging can skip objects at page boundaries. Such lists are ordered by id as well, or read in offset mode.
The following request reads the first page of 10 pipelines and the total count:
curl "$FOUNDATION4_URL/pipelines?first=10&count=true" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET"
Filters and ordering
List operations accept filters as query parameters named after the fields of the object, with an optional operator suffix. The fields that accept filters differ by operation and are listed in the API reference.
| Form | Matches objects where the field |
|---|---|
field=value | Equals the value |
field$contains=value | Contains the value, case-sensitive |
field$startswith=value | Starts with the value |
field$gt=value, field$ge=value | Is greater than, or greater than or equal to, the value |
field$lt=value, field$le=value | Is less than, or less than or equal to, the value |
Timestamps in filters use RFC 3339 format with the Z suffix, or with the + of an offset encoded as %2B. In a shell command, the $ of an operator suffix is escaped as \$ inside double quotes:
curl "$FOUNDATION4_URL/embedding-models?name\$contains=MiniLM" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET"
order_by sets the order of a list: order_by=name sorts ascending and order_by=-created_at sorts descending. The default order is id ascending. A field that the operation cannot sort by returns HTTP 400 with the message Invalid order_by parameter.
The document list also accepts status and a metadata filter written in the filter language of search requests, described in Metadata, filters and taxonomies.
Identifiers, timestamps and versions
- Identifiers. Every object identifier is a UUID. Document identifiers are time-ordered UUIDs (version 7).
- Timestamps.
created_at,updated_at,expired_atandexpirationare RFC 3339 timestamps in Coordinated Universal Time (UTC). - Versions. The
versionof a document or fragment is an integer: the creation time of the document version in microseconds since the Unix epoch. Theas_ofandversionquery parameters of document and fragment operations take the same integers.
Streaming responses
Agent execution and direct LLM queries return the generated answer in one of three formats, selected by the stream field of the request: newline-delimited JSON (NDJSON) by default, server-sent events with "sse", or plain text with false. Response formats describes each format.
A streamed response has status 200 as soon as the response starts, so an error from the model server during generation cannot change the status. An NDJSON response ends early without an error message, and a server-sent events response closes before the response is complete. A request with "stream": false returns HTTP 500 with the message Stream error: <reason> in the same situation, which makes false the better choice for testing a model server.
API documentation routes
The API server publishes a description of the API in OpenAPI format and two interactive viewers:
| Route | Content |
|---|---|
GET /openapi.json | The OpenAPI document |
GET /docs | An interactive reference built with RapiDoc |
GET /_docs-swagger | The same reference in Swagger UI |
The viewers load every asset from the API server, so both work in an air-gapped deployment. The interactive reference accepts the key identifier and secret for trying operations. Send requests from an API client describes the viewers and desktop API clients.
Deprecated routes
POST /pipelines/{id}/fragments, which reads fragments by identifier from a JSON body, is deprecated. GET /pipelines/{id}/fragments with one id query parameter for each fragment replaces the route.