Authentication
Endpoints authenticate via session cookies (browser) or Bearer token / API key (sk_sl_..., for CLIs and agents). All non-GET requests also require an X-CSRF-Token header when using cookie auth.
# All API calls require a Bearer token
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://api.soullayer.ai/v1/identity
# Self-hosted (default port 4747)
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://127.0.0.1:4747/v1/identityIdentity & State
# Get the current Identity Pack
GET /v1/identity
→ 200 { "identity": { "name": "...", "bio": "...", "style": {...} } }
# Update identity (merges into existing pack)
PATCH /v1/identity
Content-Type: application/json
{ "identity": { "bio": "New bio" } }
→ 200 { "updated": true, "version": 42 }
# Get preferences
GET /v1/preferences
→ 200 { "preferences": {...} }
# Get current state (short-lived facts)
GET /v1/state
→ 200 { "state": {...} }Compilation
Compile your SSD into platform-native formats. Registered targets: openai, claude, cursor, langchain, openclaw, ollama, plaintext.
# Compile identity for a target platform
GET /v1/compile?target=openai
GET /v1/compile?target=claude
GET /v1/compile?target=cursor
GET /v1/compile?target=langchain
GET /v1/compile?target=ollama
GET /v1/compile?target=plaintext
→ 200 { "output": "...", "truncated": false, "target": "openai" }
# If encryption is enabled and the SSD is locked:
→ 409 { "error": { "code": "E2E_ENCRYPTED", "message": "Unlock your profile at app.soullayer.ai" } }Policy
The policy pack is a set of rules evaluated against any value passing through the compiler. See Policy Grammar for rule structure.
# Get the active policy rules
GET /v1/policy
→ 200 { "rules": [...] }
# Replace the policy pack
PUT /v1/policy
{ "rules": [{ "id": "r1", "description": "Redact email", "condition": {...}, "action": "redact" }] }
→ 200 { "accepted": true }
# Evaluate a value against current policies
POST /v1/policy/eval
{ "value": "My email is alice@example.com" }
→ 200 { "allowed": true, "redactions": [...], "violations": [] }Versions
Every mutation to the SSD produces a new version. Snapshots every 50 versions, JSON Patch deltas between them.
# List versions (reverse chronological)
GET /v1/versions?limit=20
→ 200 { "versions": [{ "id": 42, "createdAt": "...", "summary": "..." }], "total": 127 }
# Get a specific version
GET /v1/versions/:id
→ 200 { "id": 42, "ssd": {...} }
# Diff two versions
GET /v1/versions/:from/diff/:to
→ 200 { "operations": [{ "op": "replace", "path": "/identity/bio", "value": "..." }] }
# Rollback to a prior version
POST /v1/versions/:id/rollback
→ 200 { "rolledBackTo": 41 }Adaptation
LLM-powered learning from conversation transcripts. Proposals are JSON Merge Patches you approve or reject.
# Ingest a transcript (LLM-powered proposals)
POST /v1/adapt/ingest
{ "transcript": "USER: ...\nASSISTANT: ...", "source": "manual" }
→ 200 { "proposals": [{...}] }
→ 429 {
"error": { "code": "RATE_LIMIT_EXCEEDED", "message": "..." },
"resetsAt": "2026-04-17T00:00:00.000Z",
"dailyLimit": 10, "todayCount": 10
}
# List proposals
GET /v1/adapt/proposals?status=pending&limit=20
GET /v1/adapt/proposals?limit=50
# Approve / reject
POST /v1/adapt/proposals/:id/approve
POST /v1/adapt/proposals/:id/reject
# Auto-approve all pending above a confidence threshold
POST /v1/adapt/auto-approve
{ "threshold": 0.85 }
→ 200 { "approved": 3, "proposals": [...] }
# Stats (includes daily quota)
GET /v1/adapt/stats
→ 200 {
"total": 42, "pending": 3, "approved": 30, "rejected": 6, "autoApproved": 3,
"quota": { "todayCount": 5, "dailyLimit": 10, "remaining": 5, "resetsAt": "..." }
}MCP
Per-user Model Context Protocol server. SSE transport authenticated with an API key. See MCP Setup.
# Open an SSE MCP session (API-key auth)
GET /v1/mcp/sse
Authorization: Bearer sk_sl_...
→ event stream
# Management (cookie-auth, dashboard-only)
GET /v1/mcp/connections
→ 200 { "connections": [{ "id": "...", "connectedAt": "..." }], "limit": 3, "plan": "pro" }
DELETE /v1/mcp/connections/:id
→ 200 { "disconnected": true }Encryption
End-to-end encryption is optional (Pro+). The browser derives a 256-bit key via Argon2id and encrypts the SSD with AES-256-GCM. The server only ever stores ciphertext, a salt, and a verify-hash.
# Get encryption status
GET /v1/encryption/status
→ 200 { "enabled": true, "salt": "...", "recoveryConfigured": true }
# Enable encryption (client derives key, encrypts, sends ciphertext only)
POST /v1/encryption/enable
{
"encryptedSSD": "...",
"salt": "...",
"verifyHash": "...",
"recovery": { "salt": "...", "verifyHash": "...", "wrappedSSD": "..." }
}
# Change passphrase (client re-encrypts)
POST /v1/encryption/change-passphrase
{ "reEncryptedSSD": "...", "newSalt": "...", "newVerifyHash": "..." }
# Recovery flow (lost passphrase, using 24-word mnemonic)
GET /v1/encryption/recovery/info → { salt, verifyHash }
POST /v1/encryption/recovery/unwrap { verifyHash } → { wrappedSSD }
POST /v1/encryption/recovery/rotate { salt, verifyHash, wrappedSSD } → { rotated: true }
# Disable (requires client-decrypted SSD)
POST /v1/encryption/disable
{ "decryptedSSD": {...} }API Keys
API keys are hashed with SHA-256 on storage. The raw key is returned once at creation and cannot be retrieved later.
# List API keys (metadata only — raw key only returned once on creation)
GET /v1/api-keys
→ 200 { "keys": [{ "id", "name", "prefix", "scopes", "lastUsedAt", "createdAt" }] }
# Create a new key
POST /v1/api-keys
{ "name": "Cursor IDE", "scopes": ["mcp", "compile"] }
→ 201 { "id": "...", "key": "sk_sl_...", "prefix": "sk_sl_abc" }
# Revoke
DELETE /v1/api-keys/:idAuth lifecycle
# Authentication endpoints
POST /v1/auth/register { email, password, name }
POST /v1/auth/login { email, password } → sets sl_access/sl_refresh cookies
POST /v1/auth/logout
POST /v1/auth/refresh → rotates session
POST /v1/auth/resend-verification { email }
DELETE /v1/auth/account { password, confirmation } → schedules deletion (30d grace)
POST /v1/auth/account/cancel-deletion
GET /v1/auth/export → GDPR Article 20 data bundle (JSON download)
GET /v1/auth/csrf-token → issues X-CSRF-Token for non-GET requestsError codes
All error responses use { "error": { "code": "...", "message": "..." } }.
AUTH_REQUIRED 401 Missing or invalid session / API key
VALIDATION_FAILED 400 Request body failed schema validation
FORBIDDEN 403 Valid credentials, insufficient permissions
NOT_FOUND 404 Resource does not exist
CONFLICT 409 State conflict (e.g., already enabled)
E2E_ENCRYPTED 409 SSD is locked client-side, unlock required
RATE_LIMIT_EXCEEDED 429 Plan quota exhausted (see resetsAt)
CONNECTION_LIMIT 429 Too many concurrent MCP sessions
PLAN_LIMIT_EXCEEDED 403 Feature gated behind higher plan
DUPLICATE_TRANSCRIPT 409 Transcript SHA-256 hash already processed
INTERNAL_ERROR 500 Unexpected server error (check logs)