Create an API key
POST
/v1/api-keysMints a new org-owned API key. Beyond the `api-keys:manage` scope check, the CALLING credential's creator must currently hold an owner/admin membership in the organization — a key minted while its creator was an admin stops being able to mint further keys the moment that user is demoted (403 `insufficient_scope`). Supports the optional `Idempotency-Key` header; note the replay caution: a replayed response includes the ORIGINAL plaintext `key` again within the 24h replay window (the same tradeoff Stripe makes for idempotent secret-bearing responses) — treat a stored Idempotency-Key as equally sensitive to the secret itself.
Required scopes
api-keys:manage
Availability
All plans
Quota cost
1 API call
Idempotency
Optional Idempotency-Key
Request
Request body
| Field | Type | Description |
|---|---|---|
namerequired | string | Caller-given label for the key.e.g. CI deploy bot |
scopes | "workspaces:read" | "tasks:read" | "tasks:write" | "members:read" | "projects:read" | "projects:write" | "usage:read" | "api-keys:manage" | "webhooks:read" | "webhooks:write"[] | Subset of scopes to grant. Defaults to every scope EXCEPT `api-keys:manage` — key-minting is never granted by default and must be requested explicitly. |
test_mode | boolean | Mint a `gid_test_…` sandbox-prefixed key instead of a live one. |
expires_in_days | integer | Expiry in days from now (1-365). Omit for no expiry. |
▸ ▾ Raw request schema
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Caller-given label for the key.",
"example": "CI deploy bot"
},
"scopes": {
"description": "Subset of scopes to grant. Defaults to every scope EXCEPT `api-keys:manage` — key-minting is never granted by default and must be requested explicitly.",
"type": "array",
"items": {
"type": "string",
"enum": [
"workspaces:read",
"tasks:read",
"tasks:write",
"members:read",
"projects:read",
"projects:write",
"usage:read",
"api-keys:manage",
"webhooks:read",
"webhooks:write"
]
}
},
"test_mode": {
"description": "Mint a `gid_test_…` sandbox-prefixed key instead of a live one.",
"type": "boolean"
},
"expires_in_days": {
"description": "Expiry in days from now (1-365). Omit for no expiry.",
"type": "integer",
"minimum": 1,
"maximum": 365
}
},
"required": [
"name"
],
"description": "Fields for minting a new API key."
}Response 201
The created key, with its plaintext secret shown once.
Fields
| Field | Type | Description |
|---|---|---|
idrequired | string | Stable id of the key row. |
namerequired | string | null | Caller-given label. |
key_prefixrequired | string | null | Non-secret fingerprint prefix shown in key lists (e.g. `gid_ab12`); never the full secret. |
scopesrequired | "workspaces:read" | "tasks:read" | "tasks:write" | "members:read" | "projects:read" | "projects:write" | "usage:read" | "api-keys:manage" | "webhooks:read" | "webhooks:write"[] | Scopes granted to this key. |
last_used_atrequired | string (date-time) | null | Last time this key authenticated a request, or null. |
expires_atrequired | string (date-time) | null | Expiry time, or null when the key never expires. |
created_atrequired | string (date-time) | ISO-8601 UTC. |
legacyrequired | boolean | True for a pre-migration user-owned key; false for a current org-owned key. Legacy keys are read-only here — mint new keys as non-legacy. |
keyrequired | string | The plaintext secret — shown EXACTLY ONCE. Store it now; it cannot be retrieved again.e.g. gid_51f3...redacted |
▸ ▾ Raw response schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Stable id of the key row."
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Caller-given label."
},
"key_prefix": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Non-secret fingerprint prefix shown in key lists (e.g. `gid_ab12`); never the full secret."
},
"scopes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"workspaces:read",
"tasks:read",
"tasks:write",
"members:read",
"projects:read",
"projects:write",
"usage:read",
"api-keys:manage",
"webhooks:read",
"webhooks:write"
]
},
"description": "Scopes granted to this key."
},
"last_used_at": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "Last time this key authenticated a request, or null."
},
"expires_at": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "Expiry time, or null when the key never expires."
},
"created_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "ISO-8601 UTC."
},
"legacy": {
"type": "boolean",
"description": "True for a pre-migration user-owned key; false for a current org-owned key. Legacy keys are read-only here — mint new keys as non-legacy."
},
"key": {
"type": "string",
"description": "The plaintext secret — shown EXACTLY ONCE. Store it now; it cannot be retrieved again.",
"example": "gid_51f3...redacted"
}
},
"required": [
"id",
"name",
"key_prefix",
"scopes",
"last_used_at",
"expires_at",
"created_at",
"legacy",
"key"
],
"additionalProperties": false,
"description": "A freshly minted API key, including its one-time plaintext secret."
}Example request
curl
curl -X POST https://app.nowgetitdone.com/v1/api-keys \
-H "Authorization: Bearer gid_YOUR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "CI deploy bot"
}'Possible errors
- 400
validation_failedRequest validation failed - 401
missing_credentialsMissing credentials - 401
invalid_api_keyInvalid API key - 401
invalid_tokenInvalid access token - 403
insufficient_scopeInsufficient scope - 403
feature_not_enabledFeature not enabled on this plan - 409
idempotency_in_progressA request with this Idempotency-Key is still in progress - 422
idempotency_key_reusedIdempotency-Key reused with a different request - 429
rate_limitedRate limit exceeded - 429
quota_exhaustedPlan quota exhausted - 500
internal_errorInternal server error