Authenticate
Every Foundation4 API request carries an API key identifier and secret. This tutorial confirms the master key, creates a key for the remaining tutorials and sets the shell variables that every example in the documentation uses. Integrators and administrators complete this tutorial before calling the API; Install for evaluation provides a deployment to call.
The tutorial follows four steps, which every new deployment repeats once:
- API access. A shell with
kubectlaccess to the cluster opens a connection to the API server. - Master key. The shell variables receive the master key from the Kubernetes secret.
- Key check.
POST /loginconfirms that the master key authenticates. - Tutorial key. The master key creates a key with fewer permissions, and the shell variables switch to the new key.
Shell variables
The examples in the documentation read the base URL and the credentials from three shell variables:
| Variable | Value |
|---|---|
FOUNDATION4_URL | The base URL of the API server, with no path or version prefix, such as http://localhost:8080 for the port forward in API access |
FOUNDATION4_API_KEY | The identifier of an API key, a universally unique identifier (UUID) |
FOUNDATION4_API_SECRET | The secret of the same API key |
The variable names differ from the FOUNDATION4AI_ prefix of server configuration, so client settings and server settings are never confused.
API access
The commands in this tutorial run in a shell on a machine with kubectl access to the cluster, such as the operator's workstation or the node of an evaluation installation. In a separate terminal on the same machine, forward a local port to the API server service:
kubectl port-forward -n foundation4ai svc/foundation4ai-api-server 8080:80
Expected result: kubectl reports that the port forward listens on 127.0.0.1:8080 and keeps running until the terminal closes. The API then answers at http://localhost:8080. A port forward that still runs from Install for evaluation serves the same purpose. An installation with an ingress or a Gateway answers at the installation's host name instead, as described in API and dashboard access, and FOUNDATION4_URL holds that URL.
Master key
The installation creates the master key from the identifier and secret in the foundation4ai-secrets Kubernetes secret. The master key holds every permission and serves only for administration, such as creating the keys that client applications use. Access control describes the master key and the permission model.
Set the shell variables with the base URL and the master key, read from the secret. The commands copy the master key into the shell variables without printing the secret. base64 -d is base64 --decode or base64 -D on some systems.
export FOUNDATION4_URL=http://localhost:8080
export FOUNDATION4_API_KEY="$(kubectl get secret foundation4ai-secrets -n foundation4ai \
-o jsonpath='{.data.FOUNDATION4AI_APP_MASTER_KEY}' | base64 -d)"
export FOUNDATION4_API_SECRET="$(kubectl get secret foundation4ai-secrets -n foundation4ai \
-o jsonpath='{.data.FOUNDATION4AI_APP_MASTER_SECRET}' | base64 -d)"
echo "$FOUNDATION4_API_KEY"
Expected result: the master key identifier, a universally unique identifier (UUID). An operator who keeps the master key in the organization's secret store, as Secrets and keys describes, can set the two variables from the store instead.
Request headers
Every request other than the root route, the health check, the metrics routes and the API documentation routes carries two headers:
x-api-key. The key identifier.x-api-key-secret. The key secret.
A request that fails authentication returns HTTP 401 with one of the following messages in the error.message field of the response body:
| Message | Cause |
|---|---|
Unauthorized: No API Key specified. | The x-api-key header is missing. |
Unauthorized: No API Secret Key specified. | The x-api-key-secret header is missing. |
Invalid key ID | The x-api-key value is not a UUID. |
Invalid API Key or Secret | The key does not exist, the secret does not match, or the key is inactive or expired. The API server also returns this message when the database is unavailable during the check. |
Key check
POST /login confirms that a key authenticates and returns the key's name and permissions on object types. The request has no body.
curl -X POST "$FOUNDATION4_URL/login" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET"
Expected result: status 201. The following response is shortened to three of the object types:
{
"message": "OK",
"key": "Master Admin Key",
"description": null,
"permissions": {
"pipelines": {"access_level": 7, "classifications": ["*"]},
"agents": {"access_level": 7, "classifications": []},
"llms": {"access_level": 7, "classifications": []}
}
}
The key field holds the key name, not the identifier. Each access_level is the sum of the permission bits: read 4, write 2 and execute 1. The classifications list is the classification allow-list, which applies to pipelines only. The order of the entries in permissions can differ from one response to the next.
The complete response for the master key lists 13 object types: the nine object types that Access control describes, plus contexts, context-messages, documents and permissions. No API operation checks these four object types in the current release, so permissions on them have no effect.
Tutorial key
The remaining tutorials use a key named getting-started with the following permissions on object types:
| Object type | Permission | Purpose in the tutorials |
|---|---|---|
embedding-providers | 4 (read) | List embedding providers |
text-splitter-providers | 4 (read) | List text splitter providers |
embedding-models | 6 (read and write) | Find the seeded embedding model and name the model in a new pipeline |
text-splitters | 7 (all) | Find the seeded text splitter, name the splitter in a new pipeline and split documents |
pipelines | 7 (all), allow-list * | Create a pipeline, add documents and search |
llms | 7 (all) | Register and query an LLM |
agents | 7 (all) | Create and run an agent |
Creating a pipeline requires write permission on the text splitter and the embedding model that the pipeline names, and adding a document requires execute permission on the text splitter. The tutorial key therefore holds write permission on both object types. Keys for client applications hold narrower permissions on individual objects, as described in Scoped keys.
Create the key with the master key:
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": "getting-started",
"description": "Key for the getting-started tutorials",
"permissions": {
"embedding-providers": 4,
"text-splitter-providers": 4,
"embedding-models": 6,
"text-splitters": 7,
"pipelines": 7,
"llms": 7,
"agents": 7
},
"classifications": ["*"]
}'
Expected result: status 201 and the new key. The secret field holds a generated 48-character string of letters, digits, + and /, shown here as a placeholder:
{
"id": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
"created_at": "2026-09-29T12:00:00.418273Z",
"updated_at": "2026-09-29T12:00:00.418273Z",
"name": "getting-started",
"description": "Key for the getting-started tutorials",
"secret": "change-me",
"active": true,
"expiration": null
}
Record the id and secret values. Foundation4 returns the secret only in this response and stores only a hash of the secret, so a lost secret cannot be recovered.
Tutorial key check
Switch the shell variables to the tutorial key and repeat the key check:
export FOUNDATION4_API_KEY=<id from the response>
export FOUNDATION4_API_SECRET=<secret from the response>
curl -X POST "$FOUNDATION4_URL/login" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET"
Expected result: status 201, with key set to getting-started and the seven permission entries of the tutorial key:
{
"message": "OK",
"key": "getting-started",
"description": "Key for the getting-started tutorials",
"permissions": {
"embedding-providers": {"access_level": 4, "classifications": []},
"text-splitter-providers": {"access_level": 4, "classifications": []},
"embedding-models": {"access_level": 6, "classifications": []},
"text-splitters": {"access_level": 7, "classifications": []},
"pipelines": {"access_level": 7, "classifications": ["*"]},
"llms": {"access_level": 7, "classifications": []},
"agents": {"access_level": 7, "classifications": []}
}
}
First search continues with the tutorial key.
Later sessions
The shell variables and the port forward last only as long as their terminals. A later session starts the port forward again, as in API access, and sets FOUNDATION4_URL, FOUNDATION4_API_KEY and FOUNDATION4_API_SECRET to the base URL and the recorded tutorial key.