Ory Talos Secure API Keys quickstart
Ory Talos Secure API Keys on Ory Network is in preview. The HTTP API is versioned v2alpha1, and request and response shapes can
change. Pin the version of the Ory SDK that you use.
Ory Talos Secure API Keys lets you issue API keys to your users and services and verify those keys from your backend. In this guide, you issue a key, verify it, and revoke it, in the Ory Console or with the API. It takes about 10 minutes.
Before you begin
You need:
- An Ory Network project. To create one, sign up for Ory Network. For an overview, see Introduction to Ory Network.
- The Owner or Developer role in the project. Viewers can't view or issue API keys. See Roles and permissions.
- For the API steps:
curlandjq, Node.js for the TypeScript examples, or Go for the Go examples.
Enable API keys
- Open API Keys → Keys in the Ory Console.
- New projects have API keys enabled, so the page shows the Create API key button. If the page shows Enable Ory Talos instead, select Enable API keys. Ory generates the signing keys for your project and sets default values, which you can change later under API Keys → Configuration in the Ory Console.
Settings take a short time to apply. If issuing your first key fails, wait a few seconds and try again. For background, see Eventual consistency.
Get a project API key and your project slug
If you use only the Ory Console, skip this step.
A project API key is required for all administrative operations. To create one, read Creating API Keys.
Copy the Project slug from Project settings → Overview in the Ory Console, then export both values as environment variables:
export PROJECT_SLUG="<your-project-slug>"
export ORY_API_KEY="ory_pat_..."
Keep the project API key on your server. It grants admin access to your Ory Network project. Never send it to browsers or mobile apps.
Issue an API key
- Ory Console
- curl
- TypeScript
- Go
-
On API Keys → Keys in the Ory Console, select Create API key.
-
Fill in these fields:
- Key name: a label for the key, for example
Quickstart key. - Actor: the identifier of the user or service that owns the key, for example
user:alice. You choose the format. Verification returns this value asactor_id, so your backend knows who sent the request. - Expiration: how long the key stays valid. The default is your project's default key lifetime, which is 7 days unless you changed it.
- Scopes under Advanced settings (optional): a comma-separated list of permissions, for example
read:data. You define what each scope means, and your backend checks them.
Leave the other fields at their defaults.
- Key name: a label for the key, for example
-
Select Create API key.
-
The Your new API key secret dialog shows the secret. Copy it now, because the Ory Console shows it only once. Then select Done, I've copied my key.


Send POST /v2alpha1/admin/issuedApiKeys with a name and an actor_id. The scopes and ttl fields are optional. Without
ttl, the default key lifetime of your project applies.
curl -s -X POST "https://$PROJECT_SLUG.projects.oryapis.com/v2alpha1/admin/issuedApiKeys" \
-H "Authorization: Bearer $ORY_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "Quickstart key",
"actor_id": "user:alice",
"scopes": ["read:data"],
"ttl": "720h"
}' | jq
The API responds with 201 Created. The secret field holds the API key:
{
"issued_api_key": {
"key_id": "5d2f8a61-3c4b-4e9a-8f17-0b6c2d9e4a73",
"name": "Quickstart key",
"actor_id": "user:alice",
"scopes": ["read:data"],
"metadata": null,
"status": "KEY_STATUS_ACTIVE",
"create_time": "2026-09-29T09:30:12Z",
"update_time": "2026-09-29T09:30:12Z",
"expire_time": "2026-10-29T09:30:12Z",
"visibility": "KEY_VISIBILITY_SECRET"
},
"secret": "ory_ak_..."
}
Install @ory/client version 1.22.56 or later:
npm install @ory/client
Call adminIssueApiKey:
import { ApiKeysApi, Configuration } from "@ory/client"
const apiKeys = new ApiKeysApi(
new Configuration({
basePath: `https://${process.env.PROJECT_SLUG}.projects.oryapis.com`,
accessToken: process.env.ORY_API_KEY,
}),
)
const { data } = await apiKeys.adminIssueApiKey({
issueApiKeyRequest: {
name: "Quickstart key",
actor_id: "user:alice",
scopes: ["read:data"],
ttl: "720h",
},
})
console.log(data.issued_api_key?.key_id, data.secret)
Install github.com/ory/client-go v1.22.48 or later:
go get github.com/ory/client-go@latest
Call AdminIssueApiKey:
package main
import (
"context"
"fmt"
"os"
ory "github.com/ory/client-go"
)
func main() {
cfg := ory.NewConfiguration()
cfg.Servers = ory.ServerConfigurations{
{URL: "https://" + os.Getenv("PROJECT_SLUG") + ".projects.oryapis.com"},
}
client := ory.NewAPIClient(cfg)
ctx := context.WithValue(context.Background(), ory.ContextAccessToken, os.Getenv("ORY_API_KEY"))
res, _, err := client.ApiKeysAPI.AdminIssueApiKey(ctx).
IssueApiKeyRequest(ory.IssueApiKeyRequest{
Name: ory.PtrString("Quickstart key"),
ActorId: ory.PtrString("user:alice"),
Scopes: []string{"read:data"},
Ttl: ory.PtrString("720h"),
}).
Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "issue request failed: %v\n", err)
os.Exit(1)
}
fmt.Println("key_id:", res.IssuedApiKey.GetKeyId())
fmt.Println("secret:", res.GetSecret())
}
The secret starts with ory_ak_ unless you changed the key prefix. Ory returns it only once, so store it or pass it to the key
owner right away. To follow the API steps, export it:
export API_KEY="ory_ak_..."
Verify the API key
- Ory Console
- curl
- TypeScript
- Go
- Open API Keys → Playground in the Ory Console.
- Under Verify API key, paste the key into the API key field and select Verify.
- To see the full response, select Show raw JSON.
Send the API key to POST /v2alpha1/admin/apiKeys:verify and authenticate the request with the project API key:
curl -s -X POST "https://$PROJECT_SLUG.projects.oryapis.com/v2alpha1/admin/apiKeys:verify" \
-H "Authorization: Bearer $ORY_API_KEY" \
-H "Content-Type: application/json" \
--data "{\"credential\": \"$API_KEY\"}" | jq
Call adminVerifyApiKey:
import { ApiKeysApi, Configuration } from "@ory/client"
const apiKeys = new ApiKeysApi(
new Configuration({
basePath: `https://${process.env.PROJECT_SLUG}.projects.oryapis.com`,
accessToken: process.env.ORY_API_KEY,
}),
)
const { data } = await apiKeys.adminVerifyApiKey({
verifyApiKeyRequest: { credential: process.env.API_KEY },
})
console.log(data.is_valid, data.actor_id, data.scopes)
Call AdminVerifyApiKey:
package main
import (
"context"
"fmt"
"os"
ory "github.com/ory/client-go"
)
func main() {
cfg := ory.NewConfiguration()
cfg.Servers = ory.ServerConfigurations{
{URL: "https://" + os.Getenv("PROJECT_SLUG") + ".projects.oryapis.com"},
}
client := ory.NewAPIClient(cfg)
ctx := context.WithValue(context.Background(), ory.ContextAccessToken, os.Getenv("ORY_API_KEY"))
res, _, err := client.ApiKeysAPI.AdminVerifyApiKey(ctx).
VerifyApiKeyRequest(ory.VerifyApiKeyRequest{Credential: ory.PtrString(os.Getenv("API_KEY"))}).
Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "verify request failed: %v\n", err)
os.Exit(1)
}
if !res.GetIsValid() {
fmt.Printf("invalid key: %s\n", res.GetErrorCode())
return
}
fmt.Printf("valid key for %s with scopes %v\n", res.GetActorId(), res.GetScopes())
}
The response looks similar to this:
{
"is_valid": true,
"key_id": "5d2f8a61-3c4b-4e9a-8f17-0b6c2d9e4a73",
"actor_id": "user:alice",
"scopes": ["read:data"],
"expire_time": "2026-10-29T09:30:12Z",
"metadata": null,
"visibility": "KEY_VISIBILITY_SECRET",
"issuer": "https://$PROJECT_SLUG.projects.oryapis.com",
"status": "KEY_STATUS_ACTIVE"
}
is_validtells you whether to accept the request. Base your decision on this field, not on the HTTP status code. An invalid key also returns200 OK.actor_idis the actor you set when you issued the key. Use it to identify the caller.scopeslists the scopes of the key. Check them before you allow an action.key_ididentifies the key. Use it to look up, update, or revoke the key.expire_timeis when the key expires, andstatusis its lifecycle state.metadataholds the JSON object attached to the key, if any.visibilityshows whether the key is a secret key or a public key.issueris the token issuer of your project, which is the project URL by default.
When is_valid is false, the response also contains error_code and error_message. A status code other than 200 means that
the request itself failed, for example because the project API key is missing or wrong.
Revoke the API key
- Ory Console
- curl
- TypeScript
- Go
- On API Keys → Keys in the Ory Console, open the menu at the end of the key's row and select Revoke.
- In the Revoke API key? dialog, optionally choose a Reason, then select Revoke key.
The key stays in the list with the status Revoked.
Send POST /v2alpha1/admin/issuedApiKeys/{key_id}:revoke with a reason. Use the key_id from the verify response:
export KEY_ID="<key-id>"
curl -s -X POST "https://$PROJECT_SLUG.projects.oryapis.com/v2alpha1/admin/issuedApiKeys/${KEY_ID}:revoke" \
-H "Authorization: Bearer $ORY_API_KEY" \
-H "Content-Type: application/json" \
--data '{"reason": "REVOCATION_REASON_KEY_COMPROMISE"}'
The API responds with 204 No Content.
Call adminRevokeIssuedApiKey with the key_id from the verify response and a reason:
import { ApiKeysApi, Configuration } from "@ory/client"
const apiKeys = new ApiKeysApi(
new Configuration({
basePath: `https://${process.env.PROJECT_SLUG}.projects.oryapis.com`,
accessToken: process.env.ORY_API_KEY,
}),
)
await apiKeys.adminRevokeIssuedApiKey({
keyId: process.env.KEY_ID!,
adminRevokeIssuedApiKeyBody: { reason: "REVOCATION_REASON_KEY_COMPROMISE" },
})
Call AdminRevokeIssuedApiKey with the key_id from the verify response and a reason:
package main
import (
"context"
"fmt"
"os"
ory "github.com/ory/client-go"
)
func main() {
cfg := ory.NewConfiguration()
cfg.Servers = ory.ServerConfigurations{
{URL: "https://" + os.Getenv("PROJECT_SLUG") + ".projects.oryapis.com"},
}
client := ory.NewAPIClient(cfg)
ctx := context.WithValue(context.Background(), ory.ContextAccessToken, os.Getenv("ORY_API_KEY"))
reason := ory.REVOCATIONREASON_REVOCATION_REASON_KEY_COMPROMISE
if _, err := client.ApiKeysAPI.AdminRevokeIssuedApiKey(ctx, os.Getenv("KEY_ID")).
AdminRevokeIssuedApiKeyBody(ory.AdminRevokeIssuedApiKeyBody{Reason: &reason}).
Execute(); err != nil {
fmt.Fprintf(os.Stderr, "revoke request failed: %v\n", err)
os.Exit(1)
}
}
You can't undo a revocation. It takes effect immediately, even when verification caching is on.
Verify the key again, in the Playground or with the request from Verify the API key. The key is no longer valid, and the response contains:
{
"is_valid": false,
"error_code": "VERIFICATION_ERROR_REVOKED",
"error_message": "The API key has been revoked."
}
Next steps
- Manage API keys: rotate keys, find the keys of one owner, revoke keys in bulk, and import keys from another system.