By default the Chap Core API has no authentication. This is intentional: the supported deployments either run DHIS2 and Chap Core together in one containerised setup, or run both locally during development. In both cases the API is reachable only from the machine or the internal network, and DHIS2 enforces authorization in front of it via the Route API.
If you need to expose Chap Core beyond that – for example connecting a cloud DHIS2 instance to a local Chap Core through a tunnel – you can enable an opt-in API token. It is the bare minimum needed to put the API somewhere it can be reached from the internet, and it should always be combined with HTTPS.
!!! warning “A token is not a substitute for network isolation” The token is a single shared secret with no rotation, no expiry and no per-client identity. Keeping Chap Core off the public internet is still the recommended setup. See Recommendation for server deployment.
Set the CHAP_API_TOKEN environment variable on the Chap Core server. When it is unset,
authentication is disabled and the API behaves exactly as before.
Generate a token with any of these:
openssl rand -hex 32
uuidgen
python -c "import secrets; print(secrets.token_hex(32))"
uuidgen is available on macOS and on Linux via util-linux, but is often missing from
slim container images – openssl is the more portable option.
Use at least 32 characters. The API has no rate limiting, so a short token can be guessed by anyone who can reach the port. Chap Core logs a warning at startup if the token is shorter than that, but still starts.
With Docker Compose, add it to your .env file and it is passed through to the chap
service:
CHAP_API_TOKEN=your-generated-token
Then restart the stack:
docker compose up -d
Send the token as a bearer token in the Authorization header:
curl -H "Authorization: Bearer your-generated-token" \
http://localhost:8000/v1/crud/datasets
Chap Core also accepts Authorization: ApiToken your-generated-token, as sent by DHIS2
routes using auth.type = api-token. Both schemes use the same CHAP_API_TOKEN value.
Scheme names are case-insensitive; the token must match exactly.
When CHAP_API_TOKEN is set, every API endpoint requires it except the ones below.
| Path | Auth | Why |
|---|---|---|
/health |
Public | Liveness probe. Called without headers by the container healthcheck and Kubernetes. |
/health/ready |
Public | Readiness probe, same reason. |
/system/info |
Public | Lets clients discover whether a token is required before authenticating. |
/docs, /redoc, /openapi.json |
Public | The API reference. It describes an open-source API, so it discloses nothing. |
| Everything else | Required | Includes /v1/** and /v2/**. |
To call the API from /docs, click Authorize, paste the token, and “Try it out” then
sends it as Authorization: Bearer <token>.
Service registration is covered too, with one accommodation for chapkit – see Relation to the service registration key.
When CHAP_API_TOKEN is configured:
| Scenario | Response |
|---|---|
Missing Authorization header |
401 Unauthorized |
Wrong token, or a scheme other than Bearer or ApiToken |
401 Unauthorized |
Both cases return the same body and a WWW-Authenticate: Bearer header, so a client can
tell “this server wants a token” apart from a network failure:
{"detail": "Missing or invalid API token"}
/system/info stays public and reports auth_required, so a client can check before it has
a token:
curl http://localhost:8000/system/info
{
"chap_core_version": "2.2.0",
"python_version": "3.13.0",
"server_date": "2026-01-01T12:00:00+00:00",
"server_time_zone_id": "Etc/UTC",
"revision": "",
"auth_required": true
}
The Modeling App reaches Chap Core through a DHIS2 Route. Configure the route’s encrypted authentication with the CHAP token:
{
"auth": {
"type": "api-token",
"token": "your-generated-token"
}
}
DHIS2 sends Authorization: ApiToken your-generated-token to Chap Core. Use the value of
CHAP_API_TOKEN, not a DHIS2 personal access token. Existing routes sending a Bearer token
continue to work. See the
DHIS2 Route API documentation
for the headers and auth fields.
CHAP_API_TOKEN and SERVICEKIT_REGISTRATION_KEY are separate secrets, configured and
checked independently, and either can be enabled without the other. See
Service Registration.
Self-registering chapkit services need special handling because servicekit can only send the
X-Service-Key header – it has no way to send Authorization. The API token is therefore
accepted in X-Service-Key as well, on any path, and on the /v2/services paths a
configured registration key is accepted there too:
| Server configuration | What a chapkit service sends |
|---|---|
Only CHAP_API_TOKEN |
SERVICEKIT_REGISTRATION_KEY set to the API token |
Only SERVICEKIT_REGISTRATION_KEY |
SERVICEKIT_REGISTRATION_KEY set to the registration key |
| Both | SERVICEKIT_REGISTRATION_KEY set to either one |
A registration key is only accepted on /v2/services paths. It cannot be used as a
general-purpose credential against /v1/**, so a model service cannot read or delete
datasets with it.
Other clients – the Modeling App, curl, the Python SDK – should use
Authorization: Bearer or Authorization: ApiToken.