Skip to main content

Credentials

A credential is an API key / secret pair, bound to one account, that authenticates API requests. Each credential carries a set of scopes and an expiry date.

Credential object​

{
"id": 1,
"accountId": 123,
"archived": false,
"apiKey": "1234567890abcdef",
"dateCreated": "2026-01-15T10:30:00Z",
"dateModified": null,
"createdBy": "user123",
"modifiedBy": null,
"deletedBy": null,
"archivedBy": null,
"expiresAt": "2026-04-15T10:30:00Z",
"scopes": ["read", "write", "execute"]
}
FieldDescription
apiKeyStable identifier sent as X-API-Key. Not secret on its own.
plaintextSecretCreate response only. The value you send as X-Secret-Key. The server stores it encrypted and never returns it again.
scopesOne or more of read, write, execute, admin.
expiresAtServer-set expiry. Defaults to 90 days after creation.
archivedArchived credentials cannot authenticate but are kept for audit.

apiSecret is never present in any response.

Endpoints​

MethodPathScopeNotes
GET/credentialsreadPaginated list; orderBy: id, date_created, date_modified, created_by, modified_by, deleted_by, expires_at
POST/credentialswrite201 with plaintextSecret
GET/credentials/{id}read
PUT/credentials/{id}writeBody {"archived": bool, "modifiedBy": "..."}; only these two fields are mutable
DELETE/credentials/{id}writeBody {"deletedBy": "..."} → 204
POST/credentials/{id}/archivewriteBody {"archivedBy": "..."} → 204

Create a credential​

createdBy and a non-empty scopes array are required; unknown or duplicate scopes return 400. Requesting admin from a credential that does not itself hold admin returns 403.

curl -X POST "https://api.scheduler0.com/api/v1/credentials" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '{
"createdBy": "user123",
"scopes": ["read", "write"]
}'

Response (201):

{
"success": true,
"data": {
"id": 2,
"accountId": 123,
"archived": false,
"apiKey": "9f8e7d6c5b4a3210",
"plaintextSecret": "s0_...",
"dateCreated": "2026-01-15T10:30:00Z",
"createdBy": "user123",
"expiresAt": "2026-04-15T10:30:00Z",
"scopes": ["read", "write"]
}
}
warning

plaintextSecret is returned only in this response. Store it in your secret manager before doing anything else.

Optional expiresInSeconds requests a shorter lifetime. The server clamps it to between 5 minutes and 90 days; you cannot extend past 90 days.

Expiry​

  • Credentials expire 90 days after creation unless a shorter expiresInSeconds was requested.
  • Requests with an expired credential return 401 credential expired.
  • The server periodically archives expired credentials, so you do not have to clean them up.
  • Credentials created during self-hosted bootstrap can have expiresAt: null and never expire. Use them for operator work only.

Scopes​

ScopeAllows
readGET on jobs, projects, credentials, executors, executions/*, async-tasks/{id}, features, ai/settings, ai/models, ai/prompt-requests, and GET /local-executors/{id}/jobs
writePOST/PUT/DELETE on jobs, projects, credentials, executors and ai/settings; POST /local-executors
executePOST ai/prompt, ai/prompt/classify, ai/schedule, ai/suggestions/analyze, ai/suggestions/time, executors/{id}/test-invoke, executions/cleanup-old-logs, local-executors/{id}/executions
admin/accounts/*, /cluster/*, POST /account/rotate-secret. Also satisfies read, write and execute.

A request with a valid credential but the wrong scope returns 403 credential missing required scope: <scope>. GET /healthcheck needs no credential at all.

Rotate a credential​

There is no rotate endpoint. Rotation is: create a replacement with the same scopes, roll the new apiKey/plaintextSecret out to your clients, then archive the old credential.

# 1. Create the replacement (save apiKey + plaintextSecret from the response)
curl -X POST "https://api.scheduler0.com/api/v1/credentials" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '{"createdBy": "user123", "scopes": ["read", "write", "execute"]}'

# 2. Archive the old one
curl -X POST "https://api.scheduler0.com/api/v1/credentials/1/archive" \
-H "Content-Type: application/json" \
-H "X-API-Key: NEW_API_KEY" \
-H "X-Secret-Key: NEW_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '{"archivedBy": "user123"}'

With the CLI: scheduler0 credentials create --scopes read,write,execute then scheduler0 credentials archive <old-id>.

List and get​

curl "https://api.scheduler0.com/api/v1/credentials?limit=10&offset=0&orderBy=date_created&orderByDirection=desc" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID"

curl "https://api.scheduler0.com/api/v1/credentials/1" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID"

The list response is { "total", "offset", "limit", "credentials": [...] }. Neither response includes the secret.

Archive or delete​

Archiving disables a credential while keeping the record; deleting removes it. Both require a body naming the actor and return 204.

curl -X POST "https://api.scheduler0.com/api/v1/credentials/1/archive" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '{"archivedBy": "user123"}'

curl -X DELETE "https://api.scheduler0.com/api/v1/credentials/1" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '{"deletedBy": "user123"}'

Do not archive or delete the credential you are currently authenticating with unless you already have a replacement.

PUT /credentials/{id} is the only other write. It takes {"archived": true|false, "modifiedBy": "..."} and can flip the archived flag in either direction (an omitted archived is treated as false, i.e. un-archive). apiKey, apiSecret, scopes and expiresAt are fixed when the credential is created: they cannot be sent in this body and any attempt to change the key or secret returns 400. To change a key or secret, rotate instead.

Troubleshooting​

SymptomCause
401 on every requestWrong apiKey/secret, wrong X-Account-ID, or the credential is archived
401 credential expiredPast expiresAt; create a replacement and archive the old one
403 credential missing required scope: writeThe credential was created without that scope; create one with the scopes you need
403 when creating an admin credentialOnly an operator (Basic Auth) or an existing admin credential can grant admin