Authentication
The Clockwork API supports two authentication modes: API keys for server-side integrations and session tokens for browser clients.
API keys
API keys start with ck_live_ and are created in the developer console under Settings → API Keys.
const client = new Clockwork({
apiKey: 'ck_live_abc123...',
})When making raw HTTP requests, pass the key as a Bearer token:
curl https://platform.clockwork-co.com/api/v1/tasks \
-H "Authorization: Bearer ck_live_abc123..."Or as the x-api-key header:
curl https://platform.clockwork-co.com/api/v1/tasks \
-H "x-api-key: ck_live_abc123..."Key security
- Store keys in environment variables, never in source code
- Keys are hashed before storage — Clockwork cannot retrieve the raw secret after creation
- Revoke keys immediately from the console if compromised
Scopes
Each API key has a set of scopes that restrict what it can do. Scopes follow the pattern {resource}:{action}:
| Scope | Grants |
|---|---|
tasks:read | GET /tasks, GET /tasks/{id} |
tasks:write | Create, update, delete, assign, complete tasks |
templates:read | Read workflow templates and versions |
templates:write | Create, publish, rollback templates |
events:read | List events, manage webhook subscriptions |
* | Full access to all resources |
A missing scope returns 403 Forbidden. Use * for scripts with full access and narrower scopes for external integrations.
Check your scopes
const { data: me } = await client.me.get()
console.log(me.apiKey?.scopes) // ['tasks:read', 'tasks:write', ...]Session tokens
For browser apps where users are authenticated with Supabase, pass a session token instead of an API key:
const client = new Clockwork({
sessionToken: async () => {
const { data } = await supabase.auth.getSession()
return data.session?.access_token ?? null
},
})Session tokens bypass scope checks — authenticated users have full access to their own data.
Rate limits
All responses include rate limit headers:
| Header | Meaning |
|---|---|
RateLimit-Limit | Max requests per period |
RateLimit-Remaining | Requests left in current period |
RateLimit-Reset | Unix timestamp when the limit resets |
When the limit is exceeded, the API returns 429 Too Many Requests. Implement exponential backoff with jitter:
async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 4): Promise<T> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await fn()
} catch (err) {
if (err instanceof ClockworkError && err.status === 429 && attempt < maxAttempts - 1) {
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000 + Math.random() * 500))
continue
}
throw err
}
}
throw new Error('Max attempts exceeded')
}Error handling
All API errors throw a ClockworkError:
import { Clockwork, ClockworkError } from '@clockwork/sdk'
try {
await client.tasks.get('non-existent-id')
} catch (err) {
if (err instanceof ClockworkError) {
console.error(err.status) // 404
console.error(err.code) // 'NOT_FOUND'
console.error(err.message) // 'Task not found'
}
}