---
title: "Create an API key."
method: POST
path: "/v1/api-keys"
tags: ["auth"]
---

# Create an API key.

`POST /v1/api-keys`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/api-keys`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Mints a dvt-issued key for the caller's user + organization. By default the key acts AS
its creator (same role, same tenant); pass `scopes` to confine it to a least-privilege
subset of capabilities (DVT-33) — recommended for MCP/customer keys. The response carries
the plaintext **once**; only its SHA-256 is stored. Admin/owner (apikey:manage) always
permitted; members may also mint their own keys when the org has `allowMemberApiKeys`
enabled (DVT-342). A key can never exceed its creator's own role capabilities. An
admin/owner may also pass `userId` to mint the key ON BEHALF OF another org member
(DVT-950) — the key then authenticates and attributes as that member, independent of
`allowMemberApiKeys` (which governs member self-service only).

## Request body

- object
  - `name` string, required: Human label, e.g. 'ci-bot'.
  - `scopes` string[]: Optional capability subset to confine the key to (intersected with the creator's role, or with `userId`'s role when set; a key can never exceed its effective owner). Omit/empty = the key holds its owner's full role capabilities. Each value must be a capability the owner's own role holds, else 400. See ApiKey.scopes for the vocabulary.
  - `expiresInDays` integer: Omit for no expiry.
  - `userId` string, uuid: Admin/owner only: mint the key for this org member instead of yourself. The key authenticates as, and attributes to, that user. Independent of `allowMemberApiKeys` (that setting governs member self-service only). Cannot target a member whose role outranks yours.

## Response `201`

The new key. Store `data.key` now — it is never shown again.

- object
  - `data` object
    - `key` string: The full secret (dvt_live_...), shown once.
    - `apiKey` ApiKey: Non-secret view of a dvt-issued API key.
      - `id` string, uuid, required
      - `name` string, required
      - `prefix` string, required: Non-secret identifier, e.g. dvt_live_ab12cd34.
      - `scopes` string[], required: The `resource:action` capabilities a request made with this key may exercise, intersected with the creator's role (DVT-33). Empty = no narrowing: the key holds its creator's full role capabilities. A key can never exceed its creator's role. The vocabulary is the capability set (mirrors Me.capabilities); this enum is drift- locked against the authz capability constants. `certification:manage`, `dashboard:certify` and `dashboard:canonicalize` appear here only because the enum mirrors the full capability set: certification cannot be granted via a delegated dvt credential (ADR-0085 D6), so minting a key scoped to any of them is rejected with 400.
      - `createdAt` string, date-time, required
      - `lastUsedAt` string, date-time
      - `expiresAt` string, date-time
      - `revokedAt` string, date-time
      - `creatorId` string, uuid: The user who created (owns) this key.
      - `creatorName` string: Display name of the creator, if set. Presentation label, not an identifier — machine logins are humanized for display (DVT-2298).
      - `creatorEmail` string, email: Email of the creator.
  - `meta` Meta: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `400`: Malformed request.
- `401`: Missing, invalid, or expired session.
- `403`: Caller's role is below admin, or `userId` was supplied without `apikey:manage`, or `userId` targets a member whose role outranks the caller's.
- `404`: `userId` is not a member of your organization (or does not exist).
- `422`: `userId` names a member who is suspended or otherwise not active.

---

[API](https://skmtc.dev/dvt/apis/dvt-rest-api.md) · [All operations](https://skmtc.dev/dvt/apis/dvt-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dvt/apis/dvt-rest-api/revisions/00016f504165?raw)
