The Ory Kratos Admin API provides complete control over identity lifecycle management. This guide covers creating, reading, updating, and deleting identities programmatically.
Admin API endpoints
The Admin API is exposed on a separate port from the public API for security:
Endpoint Method Description /admin/identitiesGET List identities /admin/identitiesPOST Create identity /admin/identitiesPATCH Batch create identities /admin/identities/{id}GET Get identity by ID /admin/identities/{id}PUT Update identity /admin/identities/{id}PATCH Patch identity /admin/identities/{id}DELETE Delete identity /admin/identities/{id}/credentials/{type}DELETE Delete credential /admin/identities/by/external/{externalID}GET Get by external ID
Create identity
Create a new identity with traits and optional credentials.
Basic creation
curl -X POST "https://kratos-admin/admin/identities" \
-H "Content-Type: application/json" \
-d '{
"schema_id": "default",
"traits": {
"email": "[email protected] ",
"name": {
"first": "Jane",
"last": "Smith"
}
}
}'
Create with password
POST /admin/identities
{
"schema_id" : "default" ,
"traits" : {
"email" : "[email protected] "
},
"credentials" : {
"password" : {
"config" : {
"password" : "secure-password-123"
}
}
},
"state" : "active"
}
Create with external ID
Link to an external system using external_id:
POST /admin/identities
{
"schema_id" : "default" ,
"traits" : {
"email" : "[email protected] "
},
"external_id" : "legacy-system-user-123" ,
"metadata_admin" : {
"imported_from" : "legacy_db" ,
"import_date" : "2024-01-15"
}
}
The external_id must be unique across all identities. Use it to maintain references to users in external systems.
{
"id" : "9f425a8d-7efc-4768-8f23-7647a74fdf13" ,
"schema_id" : "default" ,
"schema_url" : "https://example.com/schemas/default" ,
"state" : "active" ,
"state_changed_at" : "2024-01-15T09:30:00.000Z" ,
"traits" : {
"email" : "[email protected] "
},
"verifiable_addresses" : [],
"recovery_addresses" : [],
"metadata_public" : null ,
"created_at" : "2024-01-15T09:30:00.000Z" ,
"updated_at" : "2024-01-15T09:30:00.000Z"
}
Get identity
Get by ID
curl "https://kratos-admin/admin/identities/{id}"
Get with credentials
Include credential configuration using the include_credential parameter:
curl "https://kratos-admin/admin/identities/{id}?include_credential=password&include_credential=oidc"
Response includes credentials:
{
"id" : "..." ,
"credentials" : {
"password" : {
"type" : "password" ,
"identifiers" : [ "[email protected] " ],
"config" : {}
},
"oidc" : {
"type" : "oidc" ,
"identifiers" : [ "google:123456" ],
"config" : {
"providers" : [
{
"subject" : "123456" ,
"provider" : "google" ,
"initial_id_token" : "eyJ..." ,
"initial_access_token" : "ya29..." ,
"initial_refresh_token" : "1//..."
}
]
}
}
}
}
Get by external ID
Lookup identity by external identifier:
curl "https://kratos-admin/admin/identities/by/external/{externalID}"
List identities
Basic listing
curl "https://kratos-admin/admin/identities?page_size=100"
Filter by credentials identifier
Find identities by email or username:
# Exact match
curl "https://kratos-admin/admin/[email protected] "
# Similar match (experimental)
curl "https://kratos-admin/admin/identities?preview_credentials_identifier_similar=john"
Filter by IDs
Retrieve specific identities:
curl "https://kratos-admin/admin/identities?ids=id1&ids=id2&ids=id3"
The ids filter:
Maximum 500 IDs per request
Does not support pagination
Ignores duplicate or non-existent IDs
May return results in different order than requested
Filter by organization
curl "https://kratos-admin/admin/identities?organization_id={org-uuid}"
Kratos supports both page-based and keyset pagination:
Keyset pagination (recommended)
Page-based pagination
# First page
curl "https://kratos-admin/admin/identities?page_size=250"
# Get next page token from Link header
curl "https://kratos-admin/admin/identities?page_size=250&page_token=..."
Update identity
Full update (PUT)
Replace the entire identity:
curl -X PUT "https://kratos-admin/admin/identities/{id}" \
-H "Content-Type: application/json" \
-d '{
"schema_id": "default",
"traits": {
"email": "[email protected] ",
"name": {
"first": "John",
"last": "Updated"
}
},
"state": "active"
}'
Partial update (PATCH)
Update specific fields using JSON Patch :
Update traits
Update state
Update metadata
curl -X PATCH "https://kratos-admin/admin/identities/{id}" \
-H "Content-Type: application/json" \
-d '[
{
"op": "replace",
"path": "/traits/email",
"value": "[email protected] "
}
]'
Protected fields
The following fields cannot be modified via PATCH:
id
stateChangedAt
credentials
When the state field changes, state_changed_at is automatically updated to the current timestamp.
Delete identity
Delete single identity
curl -X DELETE "https://kratos-admin/admin/identities/{id}"
Deletion is permanent and irreversible. All associated sessions, credentials, and addresses are also deleted.
Delete credential
Remove a specific credential type:
curl -X DELETE "https://kratos-admin/admin/identities/{id}/credentials/password"
Supported credential types:
password
oidc
saml
totp
lookup_secret
webauthn
Identity states
Identities can be in one of two states:
State Description activeIdentity can authenticate and use the system inactiveIdentity is disabled and cannot sign in
Change state
curl -X PATCH "https://kratos-admin/admin/identities/{id}" \
-H "Content-Type: application/json" \
-d '[{
"op": "replace",
"path": "/state",
"value": "inactive"
}]'
Identities support two metadata fields:
Visible to the identity itself:
{
"metadata_public" : {
"theme" : "dark" ,
"language" : "en" ,
"notifications_enabled" : true
}
}
Only accessible through Admin API:
{
"metadata_admin" : {
"internal_id" : "emp-12345" ,
"department" : "Engineering" ,
"hire_date" : "2024-01-15" ,
"notes" : "VIP customer"
}
}
Do not store sensitive information like credit scores or health data in metadata. Use a separate secure database for sensitive data.
Error handling
Common errors
Invalid request format or validation error: {
"error" : {
"code" : 400 ,
"status" : "Bad Request" ,
"reason" : "traits.email: Does not match format 'email'"
}
}
Identity does not exist: {
"error" : {
"code" : 404 ,
"status" : "Not Found" ,
"reason" : "Unable to locate the resource"
}
}
Duplicate identifier (e.g., email already exists): {
"error" : {
"code" : 409 ,
"status" : "Conflict" ,
"reason" : "This identity conflicts with another identity that already exists."
}
}
Code examples
Complete CRUD workflow
package main
import (
" context "
" fmt "
kratos " github.com/ory/kratos/pkg/httpclient "
)
func main () {
ctx := context . Background ()
client := kratos . NewAPIClient ( kratos . NewConfiguration ())
// Create
identity , _ , err := client . IdentityAPI . CreateIdentity ( ctx ).
CreateIdentityBody ( kratos . CreateIdentityBody {
SchemaId : "default" ,
Traits : map [ string ] interface {}{
"email" : "[email protected] " ,
},
}). Execute ()
if err != nil {
panic ( err )
}
fmt . Printf ( "Created: %s \n " , identity . Id )
// Read
identity , _ , err = client . IdentityAPI .
GetIdentity ( ctx , identity . Id ). Execute ()
if err != nil {
panic ( err )
}
fmt . Printf ( "Read: %v \n " , identity . Traits )
// Update
identity , _ , err = client . IdentityAPI . UpdateIdentity ( ctx , identity . Id ).
UpdateIdentityBody ( kratos . UpdateIdentityBody {
SchemaId : "default" ,
Traits : map [ string ] interface {}{
"email" : "[email protected] " ,
},
State : kratos . IDENTITYSTATE_ACTIVE ,
}). Execute ()
if err != nil {
panic ( err )
}
fmt . Printf ( "Updated: %v \n " , identity . Traits )
// Delete
_ , err = client . IdentityAPI . DeleteIdentity ( ctx , identity . Id ). Execute ()
if err != nil {
panic ( err )
}
fmt . Println ( "Deleted" )
}
Rate limiting
Admin API endpoints have different rate limit buckets:
Bucket Endpoints Typical Limit kratos-admin-lowGET single identity High kratos-admin-mediumList operations Medium kratos-admin-highCreate, Update, Delete Lower
Exact rate limits depend on your deployment configuration. Check response headers for limit information:
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset