---
title: "Create Key"
method: POST
path: "/api/v1/organizations/{organization_id}/keys"
tags: ["organization"]
---

# Create Key

`POST /api/v1/organizations/{organization_id}/keys`

## Request

- Base URL: `https://ndi-api.nace.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://ndi-api.nace.ai/api/v1/organizations/{organization_id}/keys`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/nace/apis/ndi-service/docs/api/v1/organizations/:organization_id/keys/post.ts> (a function with typed inputs; its response is checked against the schema)

Mint a named key for the organization. The secret is returned exactly once.

Any active member may call this. ``expires_at`` must be sent, explicitly
``null`` for a key that never expires, so a key's lifetime is always a
choice somebody made.

## Path parameters

- `organization_id` string, uuid, required: Organization from POST /console/session.

## Request body

- CreateKeyRequest: Mint one named key. Both fields are required, ``expires_at`` explicitly so. Sending ``"expires_at": null`` is how a caller asks for a key that never expires. It has no default, so a key's lifetime is always a choice somebody made rather than one they never saw. The timestamp must carry an offset. Guessing a zone for a bare ``2030-01-01T00:00:00`` would move a key's death by up to a day, so the caller is asked to say which moment they mean.
  - `name` string, required: How this key is identified in listings, e.g. 'ci' or 'ingestion-prod'.
  - `description` string, nullable: Optional note about what the key is for, shown under its name in listings.
  - `expires_at` string, date-time, nullable, required: When the key should stop working, as an offset-aware timestamp. Null means never. Must be in the future.

## Response `201`

Successful Response

- CreateKeyResponse
  - `key` OrganizationKeyInfo, required: Metadata for one API key — never the secret.
    - `key_prefix` string, required: Display handle, and the id used to revoke or grant.
    - `name` string, required
    - `description` string, nullable: What the key is for, as its creator described it. Null when they said nothing.
    - `created_by_user_id` string, uuid, nullable: Who minted it. Attribution only — the key belongs to the organization.
    - `created_at` string, date-time, required
    - `expires_at` string, date-time, nullable: When it stops authenticating. Null means it never expires.
    - `status` 'active' | 'expiring_soon' | 'expired' | 'disabled', required: 'expiring_soon' inside the warning window before expires_at; 'expired' after it; 'disabled' when an admin has suspended it, which outranks both. An expired or disabled key stays listed so it can be seen and replaced, but it no longer authenticates.
    - `disabled` boolean: Whether an admin has suspended the key. Unlike revocation this is reversible.
    - `system` boolean: A key the platform manages — the signed-in member's own console session key. It is listed so the credential the console itself uses is visible, but it cannot be disabled or revoked through these endpoints.
    - `last_used_at` string, date-time, nullable: Last authenticated use. May lag by up to the auth-cache TTL (~5 minutes): cache hits skip the timestamp bump.
  - `full_key` string, required: The complete API key, shown ONLY once — it is never recoverable.

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `402`: Payment Required
- `403`: The account is disabled, or the caller's role does not permit this.
- `404`: No console account yet, or no membership in this organization.
- `409`: Conflict
- `413`: Request Entity Too Large
- `422`: The requested expiration is not in the future.
- `500`: Internal Server Error
- `503`: Service Unavailable

---

[API](https://skmtc.dev/nace/apis/ndi-service.md) - [All operations](https://skmtc.dev/nace/apis/ndi-service/llms.txt) - [OpenAPI document](https://skmtc.dev/nace/apis/ndi-service/revisions/d9d1f8ec2207?raw)
