Authentication
Every Worksible API request is authenticated with an API key bound to one organization. Keys carry an explicit list of scopes and an optional expiry. The platform stores only a hash; the plaintext is shown to you exactly once at creation.
Read scopes are available on every paid plan. Some write scopes (payments, invoices, contracts) require Global or above; AOR customers get full read + write.
Key format
Worksible keys follow a single, scannable format. The environment is in the key itself, so accidental cross-environment use is loud rather than silent.
wsk_<env>_<PREFIX>_<SECRET>
| | |
| | └── 48-char random secret. Hashed at rest. Never recoverable.
| └── 8-char public id. Shown in the dashboard for identification.
└── "live" or "test".Anatomy
- Prefix— the visible public id (e.g.
AbCd1234) identifies the row to lookup. It's safe to log. - Secret— the high-entropy random suffix. We hash it on creation; the plaintext is never persisted in cleartext, in our databases, in our logs, or anywhere else. We can't show it to you twice.
401 revoked.Sending the key
The API accepts the key in three places, in this order of preference:
- Header (recommended):
X-API-Key— a clean, dedicated header that some HTTP intermediaries treat as an opaque token (no rewriting). - Bearer token:
Authorization: Bearer <key>— fits OAuth-shaped HTTP clients without configuration. - Query parameter:
?api_key=<key>— supported but discouraged. Query strings end up in referrer headers, server access logs and browser history. Use only for one-off testing.
curl https://api.worksible.com/v1/companies/me \
-H "X-API-Key: wsk_live_AbCd1234_xR9k..."curl https://api.worksible.com/v1/companies/me \
-H "Authorization: Bearer wsk_live_AbCd1234_xR9k..."Scopes
Each key carries a list of scopes. Full access means the list is empty and the key inherits everything your plan permits. Otherwise the request is matched against the granted list and rejected with 403 missing_scope if a scope is missing. Scope names are stable across versions.
payments:write.| Scope | Group | Use | Plan |
|---|---|---|---|
| company:read | Read | Org name, plan, KYB status, legal data | Any paid plan |
| freelancers:read | Read | List + view freelancers collaborating with the org | Any paid plan |
| projects:read | Read | List + view projects, status, budget | Any paid plan |
| payments:read | Read | Inbound payments, statuses, amounts | Any paid plan |
| invoices:read | Read | Issued + received invoices | Any paid plan |
| contracts:read | Read | MSAs, SOWs, signature status | Any paid plan |
| timesheets:read | Read | Submitted + approved hours | Any paid plan |
| webhooks:read | Read | Webhook delivery history (when webhooks ship) | Any paid plan |
| freelancers:write | Write | Invite, update, remove | Any paid plan |
| projects:write | Write | Create, update, archive | Any paid plan |
| payments:write | Write | Create payment links, refund | Global+ |
| invoices:write | Write | Generate consolidated, edit drafts | Global+ |
| contracts:write | Write | Generate, send for signature | Global+ |
| timesheets:write | Write | Submit, approve, reject hours | Any paid plan |
Live vs test
Keys are bound to one environment. Live keys read and write your real production data. Test keys hit the same endpoints but only see sandbox organizations and sandbox payments. Today the test sandbox is a private preview; ask your CSM if you need it provisioned.
Rotation
Rotation is a deliberate two-step flow. We don't support “regenerate this key in place” because in-place rotation always creates a window where two systems disagree about which secret is current.
- Create a new key with the same scopes. The old one keeps working.
- Switch your client config to the new key, deploy, and confirm the new key's
lastUsedAtupdates in the dashboard. - Revoke the old key. If something goes wrong before the next deploy, an admin can restore it from the dashboard within seconds (the key row stays for audit).
Expiration
Set expiresAt at creation if the integration has a finite lifetime (a partner POC, a one-month migration, an audit). After the timestamp, requests fail with 401 expired and the row stays for audit. We send the workspace owner an email 14 days, 7 days, and 24 hours before expiry; you can't turn this off.
IP allowlist
Enterprise plans can lock each key to a list of CIDR blocks. Requests from outside the allowlist are rejected at the edge with 403 ip_not_allowed before the key is even resolved.
Security model summary
- Hashed at rest. bcrypt for legacy keys migrated from our previous platform; sha256 + constant-time compare for new keys. The validator picks the right algorithm from the row.
- Logged. Every request lands in
api_key_log: method, path, status, response time, IP, user agent. Queryable from your workspace and via the audit endpoints. - TLS only.The API answers on HTTPS only. HTTP requests are 308-redirected, but you should not rely on that — clients that send credentials over HTTP have already leaked them.
- No CORS.The public REST surface does not set CORS headers. It's for server-to-server traffic; calling it from a browser would expose the key in the source.
Next
- Conventions for IDs, money, timestamps and pagination.
- Rate limits for throttle planning.