Get your API key

Pick the right kind of key, mint a signed-in key with device login, verify it with GET /api/v1/me, and know when it expires.

Every authenticated request sends a cxy_live_ user key, and a user key is one of two kinds. A signed-in credential comes from device login and belongs in a person's own tool, such as a CLI or a desktop app. A developer API key is created in the Cruxy Console and belongs on a server.

Two kinds of key

Signed-in credentialDeveloper API key
How you get itDevice login, or native OAuth login, whichever client runs the flow.Created in the Cruxy Console.
Use it forA person's own tool, such as a CLI or a desktop app.A server, and anything that serves people other than the key's owner.
Memory and rulesOn by default.Off unless a request opts in.
ExpiresAt expires_at, 90 days after issue. Use does not renew it.Never.
Rotate or revokeNot by its holder.In the Console.
Spend limitsA monthly spend cap.The Console can limit it to chosen models and a monthly spend cap.
Limits bucketsubscriptionapikey

A signed-in credential on a server would recall its owner's memory into other people's turns, store what they say in that memory, and apply the owner's organization rules, unless every request sends memory: {"recall": false, "capture": false} and rules: false. A headless key, billed from prepaid credit, has memory and rules off by default, like a developer API key. To see which kind a key is, read bucket on GET /api/v1/limits; see Rate limits and budgets.

Device login

Cruxy issues signed-in credentials through an RFC 8628 device-authorization flow: your client asks for a code, a person approves it in a browser, and your client polls until the key is minted. The browser and the client can be different machines.

The two device-login endpoints take no key and no Authorization header: their job is to issue one. Both are rate-limited and return 429 with rate_limited if you call them too often.

  1. Request a device code

    POST /api/v1/device/code with no body.

    curl -X POST https://api.cruxy.ai/api/v1/device/code
    {
      "device_code": "cxy_dev_QK3f...redacted",
      "user_code": "WDJB-MJHT",
      "verification_uri": "https://cruxy.ai/device",
      "verification_uri_complete": "https://cruxy.ai/device?user_code=WDJB-MJHT",
      "expires_in": 600,
      "interval": 5
    }
    FieldMeaning
    device_codeThe code your client polls with. The person approving never needs it.
    user_codeThe short code the person confirms in the browser.
    verification_uriThe approval page. The person types user_code there.
    verification_uri_completeThe approval page with user_code already filled in.
    expires_inSeconds until device_code expires.
    intervalSeconds to wait between polls.

    Read verification_uri, expires_in, and interval from each response rather than hardcoding the example values.

  2. Approve in the browser

    Send the person to verification_uri_complete, or to verification_uri with the user_code to type. They approve the request before expires_in seconds pass. If they decline, polling returns access_denied.

  3. Poll for the key

    POST /api/v1/device/token with the device_code, waiting interval seconds between attempts. Polling faster than the limiter allows returns 429 with rate_limited; wait the Retry-After seconds, then resume at interval.

    # DEVICE_CODE holds the device_code from step 1.
    curl -X POST https://api.cruxy.ai/api/v1/device/token \
      -H "Content-Type: application/json" \
      -d "{\"device_code\": \"$DEVICE_CODE\"}"

    Until the flow resolves, the endpoint returns 400 with an OAuth-style body carrying code, error, and request_id:

    {
      "code": "authorization_pending",
      "error": "authorization_pending",
      "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    }

    Only authorization_pending means keep polling:

    codeMeaningWhat to do
    authorization_pendingNot approved yet.Wait interval seconds and poll again.
    access_deniedThe person declined.Stop.
    expired_tokenThe device_code expired.Start a new device login.
    invalid_grantThe device_code is not valid.Start a new device login.

    A refusal can also carry reason and error_description. The one reason today is org_membership_revoked: the login was approved for an organization the account no longer belongs to. A new login succeeds, but it may bind to a different organization, so tell the person instead of retrying silently. Branch on reason; error_description is for people and is not meant to be parsed.

    On approval the endpoint returns 200 with the key:

    {
      "access_token": "cxy_live_...redacted",
      "token_type": "bearer",
      "key_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "user_id": "6f9c1e2a-1b2c-4d5e-8f90-1a2b3c4d5e6f",
      "expires_at": "2026-10-20T12:00:00.000Z",
      "org": {
        "id": "8c1a5b3e-2d4f-4a6b-9c8d-0e1f2a3b4c5d",
        "name": "Acme Corp",
        "role": "member"
      }
    }

What the token response tells you

FieldMeaning
access_tokenYour API key. Send it as Authorization: Bearer <key>.
token_typebearer.
key_idThe key's id. Safe to log.
user_idThe account the key belongs to.
expires_atWhen the key stops working, 90 days after issue. Record it with the key; see Expiry and revocation.
orgThe one organization this key is bound to: its id, name, and your role in it (owner, admin, or member).

A key is bound to exactly one organization, fixed when it is issued. That organization decides which shared memory, rules, vaults, and conversations the key can reach. A login is not always bound to the same organization as your previous one, so show org.name to the person after every login, and read org.role rather than inferring authority from anything else.

Key format

PrefixWhat it is
cxy_live_A user key, either kind: what you send as a Bearer token.
cxy_svc_A service token, minted by an operator. It is not a user key, and an endpoint that expects one rejects the other with 401.

Treat everything after the prefix as opaque.

Verify the key

GET /api/v1/me echoes the identity behind a key. Call it once after you store a new key to confirm it works end to end.

curl https://api.cruxy.ai/api/v1/me \
  -H "Authorization: Bearer $CRUXY_API_KEY"
{
  "api_key_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "user_id": "6f9c1e2a-1b2c-4d5e-8f90-1a2b3c4d5e6f"
}

A 200 means the key works. A 401 unauthorized means it is missing, malformed, unknown, expired, or revoked: the same answer for each. Check the Authorization header, then the key itself, then its expires_at.

Expiry and revocation

  • A signed-in credential stops working at expires_at, 90 days after issue. The time is fixed when the key is issued: use does not extend it, and there is no refresh grant. Prompt for a new device login before it passes.
  • A developer API key does not expire. Rotate or revoke it in the Console.
  • You cannot revoke a signed-in credential yourself today. Its holder can neither rotate nor revoke it, so it ends only at expires_at, when its organization membership ends, or when Cruxy support revokes it. If one leaks, ask Cruxy support to revoke it.
  • An expired, revoked, or unknown key is 401 unauthorized, with WWW-Authenticate: Bearer. A key whose owner's account is suspended is 403 account_suspended.
  • Revocation applies from the next request, since nothing is cached. A stream already running is not cut.

What a key is allowed to do

A key can carry restrictions. A request outside them is refused:

RestrictionRefusal
Allowed scopes: the endpoint families the key may call, and whether it may use memory, rules, or files403 key_scope_forbidden, naming required_scope when a request field asked for a family the key lacks
Model allowlist: the tiers the key may call403 model_not_allowed
Read-only: the key may not perform billable operations403 key_read_only
Monthly spend cap429 key_spend_cap_exceeded

A 403 never clears by waiting; re-scope the key or issue a new one. For a developer API key, the Console sets the model allowlist and the monthly spend cap.

Memory recall, memory capture, and your organization's standing rules are on by default for a signed-in credential, which includes every key device login mints, and off by default for a developer API key. To get the same behavior from any key, set memory and rules on each chat request; see Memory and Standing rules.

Keep the key safe

  • Treat it like a password. Anyone who holds it can call the API as you.
  • Keep it on the server. Never ship a key to a browser. Put a server route between the browser and the gateway that adds the key; see Streaming chat in the browser.
  • Read it from the environment. Keep it in an environment variable or a secrets manager, never in source control. Every sample on this site reads CRUXY_API_KEY.
  • Use a developer API key on a server. A signed-in credential carries its owner's memory and rules into every request, expires, and cannot be revoked by its holder.
  • Store it the moment it is issued. The token endpoint returns it once; if you lose it, run device login again for a new key.
  • Log the key_id, never the key. key_id and expires_at identify a key and tell you when to replace it.
  • Use one key per environment. Separate keys for local, staging, and production keep each key_id meaningful in your logs.
  • Issue the narrowest key a service needs. A key whose allowed scopes omit memory can never read or write anyone's memory through chat.
  • Send it only to https://api.cruxy.ai.

Next steps

Was this page helpful?