# Issue, verify, and revoke an API key with the Ory Talos open-source edition

This guide uses Docker Compose with a SQLite backend, so you need `git` and Docker installed. Examples use the Ory Talos CLI, with curl as an alternative.

To run the commercial (OEL) edition with Postgres, see the [Commercial quickstart](/content/docs/talos/quickstart/docker-commercial/index.html) instead.

## Start the server

Clone the repository to fetch the Docker Compose file, then start the stack:

```shell
git clone https://github.com/ory/talos.git

cd talos

docker compose -f docker-compose.oss.yaml up --build -d
```

This starts Ory Talos with SQLite and Jaeger for tracing (UI at [http://localhost:16686](http://localhost:16686/)). Migrations run automatically. Ory Talos has no built-in web UI; manage keys with the API or CLI. For a web console, see [Editions](/content/docs/talos#editions/index.html).

The server listens on `http://localhost:4420`. Wait for it to become healthy:

```shell
for i in $(seq 1 30); do

if curl -sf http://localhost:4420/health/alive > /dev/null 2>&1; then

echo "Server is ready"

break

fi

sleep 1

done
```

Check that it responds:

```shell
curl -sf "$TALOS_URL/health/alive" | head -c 200
```

## Issue an API key

Create an API key through the admin surface:

- CLI
- curl

```shell
RESPONSE=$(talos keys issue "My first key" \

--actor quickstart-user \

--scopes "read:*,write:*" \

--ttl 168h \

--format json \

-e "$TALOS_URL" 2>/dev/null)

echo "$RESPONSE" | jq .

export API_SECRET=$(echo "$RESPONSE" | jq -er '.secret')

export KEY_ID=$(echo "$RESPONSE" | jq -er '.issued_api_key.key_id')
```

```shell
# Issue a key and capture the response

RESPONSE=$(curl -s -X POST "$TALOS_URL/v2alpha1/admin/issuedApiKeys" \

-H "Content-Type: application/json" \

-d '{

"name": "My first key",

"actor_id": "quickstart-user",

"scopes": ["read:*", "write:*"],

"ttl": "168h"

}')

echo "$RESPONSE" | jq .

# Save the secret and key ID for later steps

export API_SECRET=$(echo "$RESPONSE" | jq -er '.secret')

export KEY_ID=$(echo "$RESPONSE" | jq -er '.issued_api_key.key_id')
```

The response contains two parts:

- `issued_api_key` — the key metadata (`key_id`, name, actor, scopes, expiration).
- `secret` — the full API key credential. It is shown once. Store it securely.

## Verify the key

Send the secret to the verify endpoint to check that the key is active:

- CLI
- curl

```shell
talos keys verify "$API_SECRET" -e "$TALOS_URL"
```

```shell
VERIFY_RESPONSE=$(curl -s -X POST "$TALOS_URL/v2alpha1/admin/apiKeys:verify" \

-H "Content-Type: application/json" \
  
  -d '{"credential":"$API_SECRET"}')

echo "$VERIFY_RESPONSE" | jq .
```

The response sets `is_valid` to `true` and returns the key's metadata (actor, scopes, expiration).

## Revoke the key

Revoke the key through the admin surface, using its ID:

- CLI
- curl

```shell
talos keys revoke "$KEY_ID" --reason superseded -e "$TALOS_URL"
```

```shell
curl -s -X POST "$TALOS_URL/v2alpha1/admin/apiKeys/${KEY_ID}:revoke" \
  
  -H "Content-Type: application/json" \
  
  -d '{"reason":"REVOCATION_REASON_SUPERSEDED"}'

echo ""

echo "Key revoked"
```

Verify that the revoked key no longer passes. The `--no-cache` flag (and the `Cache-Control: no-cache` header in the curl example) forces a fresh database lookup, bypassing any verification cache:

- CLI
- curl

```shell
talos keys verify "$API_SECRET" --no-cache -e "$TALOS_URL" || true

echo "Revocation confirmed"
```

```shell
REVOKE_CHECK=$(curl -s -X POST "$TALOS_URL/v2alpha1/admin/apiKeys:verify" \
  
  -H "Content-Type: application/json" \
  
  -H "Cache-Control: no-cache" \
  
  -d '{"credential":"$API_SECRET"}')

echo "$REVOKE_CHECK" | jq .

# Verify the key is no longer active

if echo "$REVOKE_CHECK" | jq -e '.is_valid == false' > /dev/null 2>&1; then

echo "Revocation confirmed"

else

echo "ERROR: Key should have been revoked"

exit 1
fi
```

The secret is still cryptographically valid, but verification now fails because the server checks revocation status on a fresh database lookup. When a verification cache is enabled, a revoked key can still pass until its cached entry expires, which is why this step bypasses the cache.

## Stop the server

```shell
docker compose -f docker-compose.oss.yaml down
```

To also remove all data volumes:

```shell
docker compose -f docker-compose.oss.yaml down -v
```

## Next steps

- [Integration guide](/content/docs/talos/integrate/index.html) — detailed API walkthrough for all credential operations
- [Operations guide](/content/docs/talos/operate/index.html) — install, configure, and deploy Ory Talos in production
- [Architecture](/content/docs/talos/concepts/architecture/index.html) — how the admin and self-service surfaces work
