Skip to main content

Access control

Foundation4 authenticates every API request with an API key and authorizes the request with the permissions that the key holds. This page describes API keys, the read, write and execute permissions, the permissions that each common operation requires, classification allow-lists and the master key. Administrators need this page to create keys for client applications, and integrators need this page to request the permissions an integration requires.

API keys​

An API key consists of an identifier in universally unique identifier (UUID) format and a secret. Every request carries both, in the x-api-key and x-api-key-secret headers. Foundation4 stores each secret as an Argon2 hash and returns the secret once, in the response that creates the key. A lost secret cannot be recovered or regenerated; the key is replaced with a new key.

A key is created with POST /api-keys. The request contains the following fields.

FieldRequiredDescription
nameYesA name that is unique across the deployment
descriptionNoFree text for administrators
expirationNoAn expiration time in RFC 3339 format. A key without an expiration time does not expire.
permissionsNoPermissions on object types, as an object that maps each object type to a permission level, such as {"agents": 4}
classificationsNoThe classification allow-list for the pipelines object type, described in Classification allow-lists. Without the field, the allow-list is empty and the key can use no classification.

The response has status 201 and contains the key identifier in id and the secret in secret. POST /login with the new key's headers confirms that the key authenticates. The response has status 201 and contains the key's name and the key's permissions on object types. The order of the entries in permissions can differ from one response to the next.

Permissions​

A permission is a combination of three bits, written as the sum of the bits:

PermissionValueGrants
Read4Listing and retrieving objects, and reading documents
Write2Creating, changing and deleting objects
Execute1Using objects: adding documents, searching, running agents and querying LLMs

A key with read and execute permission on a pipeline holds the value 5, and a key with all three permissions holds 7.

A permission applies at one of two scopes:

  • Object type. The permission applies to every object of the type, including objects created later. Creating an object requires write permission at this scope.
  • Individual object. The permission applies to one object, identified by the object's ID.

The permission of a key on an object combines the key's permission on the object type with the key's permission on the object. The object types are agents, api-keys, embedding-models, embedding-providers, llms, pipelines, taxonomies, text-splitter-providers and text-splitters. Permissions on a pipeline also govern the documents and fragments in the pipeline. The master key also holds permissions on contexts, context-messages, documents and permissions, which POST /login lists; no operation checks these four object types in the current release.

A list request returns only the objects that the key can read. A request that lacks a required permission returns HTTP 403 or HTTP 404.

Permissions by operation​

The following table lists the permissions that common operations require.

OperationRequired permissions
List or retrieve objectsRead on the object
Create an objectWrite on the object type
Create an embedding model or a text splitterWrite on the object type and on its provider type, embedding-providers or text-splitter-providers
Change or delete an objectWrite on the object
Add a document or a new versionRead and execute on the pipeline, execute on the text splitter that processes the document, and the document's classification in the allow-list
Expire a documentWrite and execute on the pipeline, and the document's classification in the allow-list
Delete a documentRead, write and execute on the pipeline, and the document's classification in the allow-list
Search a pipelineRead and execute on the pipeline
Execute an agentExecute on the agent, the pipeline and the LLM
Query an LLM or use the OpenAI-compatible endpointExecute on the LLM
Test an embedding modelRead and execute on the embedding model
Change the classifications of a pipelineWrite on the pipeline, and * in the allow-list

A key for a client application that only searches one pipeline therefore holds 5 on that pipeline. A key for a service that keeps a pipeline current holds 7 on the pipeline, so that the service can add, expire and delete documents, and 1 on the pipeline's text splitter.

Classification allow-lists​

A permission on a pipeline can carry a classification allow-list: the classifications of the pipeline that the key may use. The value * permits every classification of the pipeline, including classifications added later. The allow-list applies to the following operations:

  • Documents. Adding, expiring and deleting a document requires the document's classification in the allow-list. Expiring or deleting every document of a pipeline requires every classification of the pipeline.
  • Classification management. Reading the classifications of a pipeline with GET /pipelines/{id}/classifications requires *, because the response includes the inheritance relationships. Adding and removing classifications also requires *.

Search requests name the reader's classifications explicitly, as described in Classifications.

An allow-list of specific classifications applies to a permission on an individual pipeline. On the pipelines object type, the allow-list ["*"] permits every classification of every pipeline.

Scoped keys​

A key for a client application is created in two steps: POST /api-keys creates the key, and POST /api-keys/{id}/permissions grants permissions on individual objects with the allow-list. The shell variables in the examples are described in Authenticate. The following request creates a key for a support portal:

curl -X POST "$FOUNDATION4_URL/api-keys" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"name": "support-portal-search",
"description": "Search key for the support portal",
"expiration": "2027-09-30T00:00:00Z"
}'

The response has status 201 and contains the new key. The secret field holds a generated 48-character string, shown here as a placeholder:

{
"id": "14b7d6bc-8cf7-4766-86ac-2cc66530baee",
"created_at": "2026-09-29T12:05:12.734519Z",
"updated_at": "2026-09-29T12:05:12.734519Z",
"name": "support-portal-search",
"description": "Search key for the support portal",
"secret": "change-me",
"active": true,
"expiration": "2027-09-30T00:00:00Z"
}

The following request grants read and execute permission on one pipeline, with an allow-list of two classifications. $NEW_KEY_ID holds the id of the new key.

curl -X POST "$FOUNDATION4_URL/api-keys/$NEW_KEY_ID/permissions" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"permissions": [
{"object_type": "pipelines", "object_id": "'"$PIPELINE_ID"'", "permission": 5}
],
"classifications": ["public", "internal"]
}'

The response has status 200 and an empty body. GET /api-keys/{id}/permissions returns every permission entry of the key, each with object_type, object_id, permission and, for pipelines, classifications. On other object types, classifications is null.

The permissions request follows these rules:

  • Entries. Each entry names an object type, an object ID and a permission value. The object ID 00000000-0000-0000-0000-000000000000 denotes the object type scope.
  • Updates. An entry replaces the permission of the same object type and object ID. Entries not named in the request are unchanged. A permission value of 0 removes the entry.
  • Allow-list. The classifications list applies to every pipeline entry in the request, and a request without the list removes the allow-list of those entries, so that the key can use no classification of those pipelines. A request that includes a pipeline entry therefore always carries the intended list, and pipelines that need different lists are granted in separate requests.
  • Atomicity. Foundation4 applies all entries of a request in one transaction. If any entry is refused, no entry changes.

Granting limits​

A key can grant only what the key holds. Each granted permission must be contained in the granting key's permission on the object type, or on the object when an existing key's permissions are changed. When the granting key's allow-list on pipelines is not *, each granted classification must be in the granting key's allow-list. Creating keys requires write permission on the api-keys object type, and changing a key's permissions requires write permission on that key.

The key that creates a pipeline receives all three permissions on the new pipeline, with the allow-list *. Creating any other object grants no permission to the creating key; the creating key relies on the key's permissions on the object type.

Key lifecycle​

PATCH /api-keys/{id} changes the name, description, active and expiration fields of a key. The secret cannot be changed. Deactivating a key with "active": false and deleting a key with DELETE /api-keys/{id} take effect on the next request. A key cannot delete itself.

A key is replaced by creating a new key with the same permissions, moving the client application to the new key and deleting the old key.

Master key​

The master key is the administration key of a deployment. The master key holds all three permissions on every object type, with the allow-list *, and is used to create the scoped keys that client applications use.

The identifier and secret of the master key are set in the configuration keys app.master.key and app.master.secret, or in the environment variables FOUNDATION4AI__APP__MASTER__KEY and FOUNDATION4AI__APP__MASTER__SECRET. The identifier must be a UUID. The installation creates the master key with the foundation4ai admin create-admin-api-key command. The API server and the workers authenticate with the master key at startup, so the master key must remain active and unexpired for the deployment to start.

The master key does not belong in client application configuration. Secrets and keys describes how the master key is stored.