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.

typescript
const client = new Clockwork({
  apiKey: 'ck_live_abc123...',
})

When making raw HTTP requests, pass the key as a Bearer token:

bash
curl https://platform.clockwork-co.com/api/v1/tasks \
  -H "Authorization: Bearer ck_live_abc123..."

Or as the x-api-key header:

bash
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}:

ScopeGrants
tasks:readGET /tasks, GET /tasks/{id}
tasks:writeCreate, update, delete, assign, complete tasks
templates:readRead workflow templates and versions
templates:writeCreate, publish, rollback templates
events:readList 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

typescript
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:

typescript
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:

HeaderMeaning
RateLimit-LimitMax requests per period
RateLimit-RemainingRequests left in current period
RateLimit-ResetUnix timestamp when the limit resets

When the limit is exceeded, the API returns 429 Too Many Requests. Implement exponential backoff with jitter:

typescript
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:

typescript
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'
  }
}