> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sahlfinancial.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create an API key

> **Who can call it:** `tenant_admin`, `tenant_api_manager` or `platform_admin`.

The secret (`raw_key`) is returned **once**. A `kyc:*` scope needs a workspace enabled for the Partner KYC API, otherwise 403 `kyc_scope_not_allowed`. The key is used with the [Partner API](/api-reference/introduction), not with these routes.

**Auth:** dashboard session (`Authorization: Bearer <access token>`). Not available with a partner API key.



## OpenAPI

````yaml /openapi-console.json post /v1/api-keys
openapi: 3.1.0
info:
  title: Sahl Console API
  version: 0.1.0
  description: >-
    **Used by the Sahl console. Not available with partner API keys. Contact
    Sahl if you need programmatic access.**


    These are the routes the Sahl console calls with a signed-in user's session.
    They are documented so that a developer or a workspace admin can see what
    the console does, with the real request and response models. They are
    **not** part of the [Partner API](/api-reference/introduction): an API key
    is refused here (401), there is no try-it, and the shapes can change with
    the console.


    Authentication: `Authorization: Bearer <access token>`, the JWT the console
    gets from `POST /v1/auth/login` (30 minutes; renewed with `POST
    /v1/auth/refresh`, refresh token valid 7 days). There is no cookie. The role
    of the user is read from the user's record on every call, not from the
    token. Every call is scoped to the workspace of the user.


    Common answers: 401 (no or bad session), 403 `Insufficient permissions`
    (role), 422 (validation), 429 (100 requests per minute and IP). Error bodies
    have the same three shapes as the Partner API: see [Errors](/errors).
servers:
  - url: https://app.sahlfinancial.com/api
    description: Console host. Same host as the Partner API.
security:
  - dashboardSession: []
tags:
  - name: Bank connections
    description: Simulated in the Sandbox; 409 `bank_connect_unavailable` elsewhere.
  - name: Cases
    description: Client files that group documents, a score and a decision.
  - name: Documents
    description: Upload, status, result and download.
  - name: Webhooks
    description: Manage the endpoints that receive events.
  - name: API keys
    description: Create, rotate and revoke the keys used with the Partner API.
  - name: Scoring policies
    description: Weights and thresholds of the credit score.
  - name: Flows
    description: Verification flows (graphs) and their simulation.
paths:
  /v1/api-keys:
    post:
      tags:
        - API keys
      summary: Create an API key
      description: >-
        **Who can call it:** `tenant_admin`, `tenant_api_manager` or
        `platform_admin`.


        The secret (`raw_key`) is returned **once**. A `kyc:*` scope needs a
        workspace enabled for the Partner KYC API, otherwise 403
        `kyc_scope_not_allowed`. The key is used with the [Partner
        API](/api-reference/introduction), not with these routes.


        **Auth:** dashboard session (`Authorization: Bearer <access token>`).
        Not available with a partner API key.
      operationId: createApiKey
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiKeyCreate'
            example:
              name: Back office (test)
              scopes:
                - kyc:extract
                - kyc:verify
        required: true
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyCreateResponse'
              example:
                key:
                  id: 9a2e6c4b-8d1f-4b57-9c3a-5e7f1d0b2a68
                  tenant_id: 0b6f5a3e-7c1d-4e0a-9d2f-3a1c5e8b7f10
                  name: Back office (test)
                  prefix: sk_test_Ab3d
                  scopes: kyc:extract,kyc:verify
                  bound_identity: null
                  status: active
                  last_used_at: null
                  expires_at: null
                  created_at: '2026-10-07T09:14:22Z'
                raw_key: sk_test_Ab3d...redacted
        '401':
          description: No session, expired token, ended session or inactive account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: Invalid or expired token
        '403':
          description: >-
            Role not allowed, email not verified (`email_not_verified`), or
            `kyc:*` scope on a workspace not enabled for the Partner KYC API
            (`kyc_scope_not_allowed`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail:
                  code: kyc_scope_not_allowed
                  message: This workspace is not enabled for the partner KYC API.
        '422':
          description: Unknown scope or an invalid `bound_identity`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail:
                  - type: value_error
                    loc:
                      - body
                      - scopes
                    msg: 'Value error, unknown scope(s): foo'
                    input:
                      - foo
        '429':
          description: >-
            More than 100 requests per minute from one IP address. The body has
            no `detail`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitError'
              example:
                code: rate_limit_exceeded
                message: Too many requests. Please slow down.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds to wait.
components:
  schemas:
    ApiKeyCreate:
      properties:
        name:
          type: string
          maxLength: 255
          minLength: 1
        scopes:
          anyOf:
            - items:
                type: string
              type: array
            - type: 'null'
        bound_identity:
          anyOf:
            - type: string
            - type: 'null'
      type: object
      required:
        - name
    ApiKeyCreateResponse:
      properties:
        key:
          $ref: '#/components/schemas/ApiKeyResponse'
        raw_key:
          type: string
      type: object
      required:
        - key
        - raw_key
      description: Returned only at creation time — includes the raw secret.
    Error:
      type: object
      description: >-
        FastAPI error body. `detail` is a string, a list (422), or an object
        with `code` and `message` (extra keys possible).
      properties:
        detail:
          oneOf:
            - type: string
            - items:
                additionalProperties: true
                type: object
              type: array
            - type: object
              additionalProperties: true
              properties:
                code:
                  type: string
                message:
                  type: string
      required:
        - detail
    RateLimitError:
      type: object
      description: Top-level `code` and `message`, no `detail`.
      properties:
        code:
          type: string
        message:
          type: string
      required:
        - code
        - message
    ApiKeyResponse:
      properties:
        id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        name:
          type: string
        prefix:
          type: string
        scopes:
          anyOf:
            - type: string
            - type: 'null'
        bound_identity:
          anyOf:
            - type: string
            - type: 'null'
        status:
          type: string
        last_used_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
        expires_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
        created_at:
          type: string
          format: date-time
      type: object
      required:
        - id
        - tenant_id
        - name
        - prefix
        - scopes
        - status
        - last_used_at
        - created_at
  securitySchemes:
    dashboardSession:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        The access token (JWT) of a signed-in console user, from `POST
        /v1/auth/login`. It lasts 30 minutes. It is not an API key: a partner
        API key is refused here. There is no cookie.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.