API authentication
To call the TestLookup API, send a credential with each request: an access token for a signed-in user, or an API key. This page explains how to get each one, which header to send it in, and what it lets you do.
Access tokens and API keys
Section titled “Access tokens and API keys”Most endpoints accept either credential.
| Credential | Header | How you get it | Lifetime |
|---|---|---|---|
| Access token (a JWT) | Authorization: Bearer <access token> | Sign in with POST /api/v1/auth/login, then POST /api/v1/auth/mfa/verify if two-step sign-in (MFA) applies. Single sign-on with SAML also gives you one. | 12 hours by default. Renew it with POST /api/v1/auth/refresh. The refresh token lasts 7 days by default and is replaced each time you use it. POST /api/v1/auth/logout revokes it. |
| Personal API key | X-API-Key: <API key> | POST /api/v1/keys, or Settings › Account › My API keys. You need the QA engineer role or higher. | You can set an expiry of 1 to 365 days (expires_days) when you create it. Revoke it with DELETE /api/v1/keys/{key_id}. |
| Project API key | X-API-Key: <API key> | POST /api/v1/keys with a project_id. Admins only. | The same as a personal key. |
Use an access token when you work in person, and for the command-line tool after testlookup auth login. Use a personal API key for scripts and CI jobs that act as you in all your projects. Use a project API key for a CI job that sends results for one project. Sending live results (streaming ingest) needs a project API key.
API keys start with qai_. The server stores only a hash of each key and shows you the key once, when you create it.
If a request has both headers, TestLookup tries the access token first. It uses the API key only if the token is rejected with 401. An API key sent as Authorization: Bearer does not work, so always send it in X-API-Key.
For example, with your backend’s address in TL_URL (http://localhost:8000 in the default local setup) and your key in TL_API_KEY:
curl --fail-with-body "$TL_URL/api/v1/projects" -H "X-API-Key: $TL_API_KEY"What a credential can do
Section titled “What a credential can do”A credential acts as its user, with that user’s role and project memberships. There are no service accounts.
- An endpoint can require a minimum role, membership of the project, or access to a particular item. Knowing the id of a run or project does not give you access to it.
- An admin can create a key for another user, and can limit a key to one project. A key limited to a project works only for that project, and TestLookup takes the project from the key.
- Scopes limit what a key can do:
stream:writeallows the endpoints that receive live results (streaming ingest). These also need a key limited to one project.project:writeallows changes that need less than the QA lead role, such as runs, triage, suites and feedback.project:adminallows QA lead and admin requests for the key’s project, and includesproject:write.
- A key with no scopes is an older key with full access.
- A key created by a key with scopes gets at most that key’s scopes, and it cannot outlive that key. TestLookup refuses an unknown scope name with
422. - Instance administration endpoints (
/api/v1/admin/*) refuse keys that are limited to one project.
Requirements on each endpoint page
Section titled “Requirements on each endpoint page”Every endpoint page has an Authentication line near the top. It says which credential the endpoint takes, and lists any minimum role, project membership or access to a particular item that applies. Browse all endpoints.
Login form and MFA
Section titled “Login form and MFA”Sign in with POST /api/v1/auth/login. Send username and password as form fields (application/x-www-form-urlencoded), not as JSON:
curl --fail-with-body "$TL_URL/api/v1/auth/login" --data-urlencode "username=$TL_USERNAME" --data-urlencode "password=$TL_PASSWORD"A successful sign-in returns 200 with one of three bodies:
- Tokens:
access_token,refresh_token,expires_inandmust_change_password. You are signed in. - MFA challenge:
mfa_required: trueand achallenge_token. Finish signing in withPOST /api/v1/auth/mfa/verify. - Enrollment required:
mfa_enrollment_required: trueand anenrollment_token. You must set up MFA before you can sign in.
Challenge and enrollment tokens are not access tokens, so every other endpoint refuses them.
Routes with their own credentials
Section titled “Routes with their own credentials”| Route | Credential |
|---|---|
SCIM provisioning (/api/v1/scim/v2/*) | A SCIM token in Authorization: Bearer <SCIM token>. Admins create tokens with POST /api/v1/scim-tokens or in Settings › SSO and identity. SCIM works only when it is enabled on the server. |
Storage webhook, POST /webhooks/minio | The shared secret from the backend’s WEBHOOK_SECRET setting, in Authorization: Bearer <secret> or X-Webhook-Secret. |
Jira webhook, POST /api/v1/feedback/jira-webhook | An HMAC signature in X-Hub-Signature: sha256=<hmac>, made with the backend’s JIRA_WEBHOOK_SECRET. |
Shared report links (/api/v1/shared/reports/{token}) | The token in the link. No sign-in is needed. |
| Live WebSocket and event streams | See Streaming routes. |
| MCP server | Access tokens only. Over its SSE connection, each MCP client sends its own Authorization: Bearer <access token>. API keys and SCIM tokens are refused. |
Next steps
Section titled “Next steps”- Errors and retries: what
401,403and other statuses mean. - Streaming routes: authenticating WebSocket and event streams.
- All endpoints.
More detail is in the source repository on GitHub: the API guide and the security guide.