Authentication
StateCore Cloud uses a two-layer authentication model:
- Account auth (JWT) — used to manage your account and API keys via the console endpoints (
/auth/*,/keys). - API key auth — used for every
/v1/*call from your agent or application.
How API Keys Work
When you call a /v1 endpoint with your sc_live_ key, the following happens:
Your app Gateway StateCore core
| | |
| Authorization: | |
| Bearer sc_live_… | |
|----------------------->| |
| | lookup key in DB |
| | hash(key) → accountId |
| | |
| | x-user-id: <accountId> |
| |--------------------------->|
| | | execute /v1/* logic
| | response |
| response |<---------------------------|
|<-----------------------| |
- The gateway hashes the incoming key and looks it up in the database.
- If valid and not revoked, it forwards the request to the core with an
x-user-idheader set to your account ID. - The core uses
x-user-idas the tenant identifier — all data is scoped to your account.
Your sc_live_ key never reaches the core. The gateway acts as the security boundary.
Sending the API Key
Include your key as a bearer token in the Authorization request header:
curl https://api.statecore.io/v1/health \
-H "Authorization: Bearer sc_live_AbCdEfGhIjKlMnOpQrStUvWxYz..."
All /v1 endpoints require this header. See the API Reference for the full list of operations.
API Key Lifecycle
Create a key
curl -X POST https://api.statecore.io/keys \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"name": "production-agent"}'
nameis a label for your own reference.- The
keyfield in the response is thesc_live_value shown once only.
List keys
curl https://api.statecore.io/keys \
-H "Authorization: Bearer <jwt>"
Returns all keys for your account with id, name, createdAt, and revokedAt. The raw key value is never returned.
Revoke a key
curl -X DELETE https://api.statecore.io/keys/<key-id> \
-H "Authorization: Bearer <jwt>"
Revoked keys are rejected immediately at the gateway — in-flight requests complete, subsequent ones receive 401 Unauthorized.
Restricting a Key to Specific Scopes
By default an API key reaches every scope on the account. Keys differ only in that you can revoke one without revoking the others — they are a rotation mechanism, not an isolation boundary.
If you run more than one product from a single account, that matters: a key leaked from one product exposes the memory of the others. Bind a key to specific scopes and it becomes an isolation boundary.
curl -X POST https://api.statecore.io/keys \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"label": "recipe-app", "scopeIds": ["f47ac10b-...", "9c8b7a65-..."]}'
Omit scopeIds (or send an empty array) and the key is unrestricted, which is
what every key created before this feature is.
You can also tick Restrict this key to specific scopes when creating a key in the console.
What a restricted key can and cannot do
| Restricted key | |
|---|---|
| Read/write a bound scope | ✅ |
| Read/write any other scope | ❌ 404 Not Found |
GET /v1/scopes | ✅ — filtered to its bound scopes |
POST /v1/scopes (create) | ❌ 403 Forbidden |
GET /v1/memory/digests/:id/selection | ❌ 403 Forbidden |
POST /v1/reminders/:id/cancel | ❌ 403 Forbidden |
A request for a scope the key is not bound to returns 404 Not Found — the
same response as a scope that does not exist. This is deliberate: the error
cannot be used to discover which scopes exist on the account.
The three 403 cases are refused because the scope they act on cannot be
determined from the request: a digest is addressed by digest id, a cancellation
by reminder id, and creating a scope is an account-level act a restricted key
would be performing on something it could not then reach. Use an unrestricted key
for those.
Restriction is enforced at the API boundary, and any route not explicitly classified is refused for restricted keys. New endpoints are therefore unavailable to a restricted key until they are classified — they never become silently reachable.
Obtaining a JWT (Account Auth)
Account-management endpoints (/keys) require a short-lived JWT. Obtain one at login:
curl -X POST https://api.statecore.io/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "your-password"}'
# → { "access_token": "eyJhbGci..." }
Pass it as Authorization: Bearer <token>.
JWTs expire after a configurable TTL (default 24 hours on the managed cloud). Re-authenticate to get a new token.
Error Responses
| Status | Reason |
|---|---|
401 Unauthorized | Missing or invalid Authorization header / API key |
401 Unauthorized | Revoked API key |
403 Forbidden | Valid key, but the requested resource belongs to a different account |
Security Best Practices
- Never commit
sc_live_keys to source control. Use environment variables (SC_API_KEY). - Use one key per deployment environment (dev, staging, production) so you can revoke without impacting others.
- Rotate keys periodically — revoke the old key only after the new one is deployed.
- If one account serves several products, restrict each product's key to its own scopes. Otherwise a key leaked from one product exposes every other product's memory.