Authentication
Every request takes an API key as a bearer token; the key's role decides which endpoints it can call.
Authenticate by sending an API key in the Authorization header:
Bearer followed by your API key. Keys look like aak_ plus 48 hex characters.
Create keys in the dashboard under Settings → API Keys or with POST /v1/api-keys. The full lifecycle (scoping, rotation, revocation) is in API keys.
A key acts in its creator’s organization. It has its creator’s permissions, or those of the role it’s bound to with role_id. List the roles you can bind, with their ids, with GET /v1/roles. There’s no header to switch organizations; each user belongs to one.
Permissions
Each endpoint requires one permission. A key passes if its role has it.
| Permission | Endpoints | owner | admin | member | read_only |
|---|---|---|---|---|---|
generation:read | GET /v1/generation/*, GET /v1/workflows/*, GET /v1/runs, POST /v1/generation/estimate, POST /v1/workflows/products/{slug}/estimate | ✓ | ✓ | ✓ | ✓ |
generation:write | Submits, uploads, cancels, product runs | ✓ | ✓ | ✓ | |
projects:read | GET /v1/projects, GET /v1/projects/{id} | ✓ | ✓ | ✓ | ✓ |
projects:write | Create, update and archive projects | ✓ | ✓ | ||
billing:read | GET /v1/billing/* | ✓ | ✓ | ✓ | ✓ |
billing:write | PUT /v1/billing/settings, POST /v1/billing/topups | ✓ | ✓ | ||
api_keys:read | GET /v1/api-keys, GET /v1/api-keys/{id} | ✓ | ✓ | ✓ | ✓ |
api_keys:create | POST /v1/api-keys, PATCH /v1/api-keys/{id} | ✓ | ✓ | ||
api_keys:revoke | DELETE /v1/api-keys/{id} | ✓ | ✓ | ||
roles:read | GET /v1/roles/{id}, GET /v1/roles/{id}/users | ✓ | ✓ | ✓ | |
webhooks:read | GET /v1/webhooks, GET /v1/webhooks/{id}, GET /v1/webhooks/events | ✓ | ✓ | ✓ | |
webhooks:create | POST /v1/webhooks | ✓ | ✓ | ||
webhooks:update | PATCH /v1/webhooks/{id}, POST /v1/webhooks/{id}/test | ✓ | ✓ | ||
webhooks:delete | DELETE /v1/webhooks/{id} | ✓ | ✓ |
GET /v1/roles needs no permission; any valid key can list the roles. Each endpoint page names its permission. Roles are described in Organizations and projects.
Authentication failures
Authentication and permission failures don’t use the standard error envelope. They return a flat body:
| Status | Cause |
|---|---|
401 | No Authorization header, a key that doesn’t exist, a revoked, paused or expired key, a key whose creator was removed from the organization, or an account still waiting for early-access approval. |
403 | The key is valid but its role lacks the endpoint’s permission. |
The body doesn’t say which cause applies. To tell an expired key from a revoked one, look the key up in the dashboard.
Some 403s come from inside an endpoint rather than the permission check, for example binding a key to a role broader than your own. Those use the standard envelope with code auth.forbidden:
{
"error": {
"code": "auth.forbidden",
"message": "You cannot grant a role with more permissions than your own.",
"request_id": "59630881.7c2e4a"
}
}
Handle both shapes: if error is a string, it’s an auth failure; if it’s an object, read error.code. See Errors.
Examples
A member-scoped key generating an image works:
curl -X POST https://api.windpaint.ai/v1/generation/capabilities/image.generate \
-H "Authorization: Bearer $WINDPAINT_MEMBER_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "a lighthouse at dusk"}'
# 202 {"status": "queued", ...}
The same key creating a project is refused:
curl -X POST https://api.windpaint.ai/v1/projects \
-H "Authorization: Bearer $WINDPAINT_MEMBER_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Campaign Q4"}'
# 403 {"status": false, "error": "Forbidden"}
A revoked key is refused everywhere:
curl https://api.windpaint.ai/v1/projects \
-H "Authorization: Bearer aak_revoked..."
# 401 {"status": false, "error": "Unauthorized"}
Keeping keys out of clients
API keys carry your organization’s credits. Use them only from servers, CI and your own machine. There are no publishable or browser-safe keys; if a browser or mobile app needs generated media, call Windpaint from your backend and hand the client a signed asset URL from GET /v1/generation/assets/{id}/url.