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

# Record a certification by external key

> One call keyed on what an outside system holds: the person's email and the certification's title (created in the library if absent) or id. Records it with the issued date, optional expiry and score, and approves it directly; `renew: true` routes through renewal instead. Global-administrator acting identities only.

**Requires scope:** `certifications:write`.



## OpenAPI

````yaml /api-docs/openapi.v1.json post /api/v1/people-certifications/composite
openapi: 3.0.0
info:
  title: SkillsDB Public API
  description: >-
    The SkillsDB public REST API. Authenticate every request with an API key
    created in Settings → API Credentials, sent as `Authorization: Bearer
    sdb_live_…`. Errors follow RFC 9457 problem details; every `type` URI
    resolves under https://help.skillsdb.com/api-docs/errors/.
  version: '1'
  contact: {}
servers:
  - url: https://prod.skillsdbnext.com
    description: Production
  - url: https://staging.skillsdbnext.com
    description: Staging
security: []
tags: []
paths:
  /api/v1/people-certifications/composite:
    post:
      tags:
        - Person Certification
      summary: Record a certification by external key
      description: >-
        One call keyed on what an outside system holds: the person's email and
        the certification's title (created in the library if absent) or id.
        Records it with the issued date, optional expiry and score, and approves
        it directly; `renew: true` routes through renewal instead.
        Global-administrator acting identities only.


        **Requires scope:** `certifications:write`.
      operationId: recordCertificationComposite
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Any unique string (at most 200 characters). Retrying with the same
            key and identical content replays the stored response for 24h
            instead of executing twice.
          schema:
            type: string
            maxLength: 200
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompositeCertificationDto'
      responses:
        '201':
          description: The created resource, enveloped.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    $ref: '#/components/schemas/PersonCertificationDetailDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            Idempotency-Replayed:
              description: >-
                `true` when this response was replayed from a previous execution
                under the same Idempotency-Key.
              schema:
                type: string
        '400':
          description: >-
            Validation failed
            (`https://help.skillsdb.com/api-docs/errors/validation-error`)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetailsDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
        '401':
          description: >-
            Invalid credential
            (`https://help.skillsdb.com/api-docs/errors/invalid-credential`)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetailsDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
        '403':
          description: >-
            Insufficient scope
            (`https://help.skillsdb.com/api-docs/errors/insufficient-scope`) ·
            Insufficient permissions
            (`https://help.skillsdb.com/api-docs/errors/insufficient-permissions`)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetailsDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
        '404':
          description: Not found (`https://help.skillsdb.com/api-docs/errors/not-found`)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetailsDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
        '409':
          description: >-
            Idempotency key reused with different content
            (`https://help.skillsdb.com/api-docs/errors/idempotency-key-reused`)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetailsDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
        '422':
          description: >-
            Request understood but rejected by business rules
            (`https://help.skillsdb.com/api-docs/errors/unprocessable`)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetailsDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
        '429':
          description: >-
            Rate limit exceeded
            (`https://help.skillsdb.com/api-docs/errors/rate-limit-exceeded`)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetailsDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
        '500':
          description: >-
            Internal error
            (`https://help.skillsdb.com/api-docs/errors/internal-error`)
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetailsDto'
          headers:
            X-Request-Id:
              description: Unique id for this request — quote it when contacting support.
              schema:
                type: string
            RateLimit-Limit:
              description: The bucket's hourly allowance.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
      security:
        - apiKey:
            - certifications:write
components:
  schemas:
    CompositeCertificationDto:
      type: object
      properties:
        email:
          type: string
          example: ada@example.com
          description: The person's email address.
        certificationTitle:
          type: string
          example: Forklift Operator Licence
          maxLength: 100
          description: Created if no certification has this exact name.
        certificationId:
          type: string
          example: '87'
          description: An existing certification id, instead of a title.
        issuedAt:
          type: string
          example: '2026-01-15'
          description: Issued date (ISO 8601). Required.
        expiresAt:
          type: string
          example: '2028-01-15'
        scoreDescription:
          type: string
          example: Pass
          maxLength: 255
        renew:
          type: boolean
          example: false
          description: Route through renewal of the current approved record instead.
      required:
        - email
        - issuedAt
    PersonCertificationDetailDto:
      type: object
      properties:
        person:
          $ref: '#/components/schemas/PersonRefDto'
        certification:
          $ref: '#/components/schemas/CertificationRefDto'
        status:
          type: string
          enum:
            - assigned
            - awaiting_approval
            - approved
            - denied
          nullable: true
          example: approved
          description: >-
            Workflow state; null when the record exists but has never been
            graded.
        certified:
          type: boolean
          example: true
          nullable: true
          description: >-
            Pass/fail flag recorded with the certification. Certifications are
            not graded on the company scale.
        identifier:
          type: string
          example: FL-2291-B
          nullable: true
          description: Certificate number issued by the authority.
        issuedAt:
          type: string
          example: '2025-06-01T00:00:00.000Z'
          nullable: true
          description: Issued date (UTC).
        expiresAt:
          type: string
          example: '2027-06-01T00:00:00.000Z'
          nullable: true
          description: Expiry date (UTC).
        expiry:
          description: Expiry state derived from expiresAt — never from status.
          allOf:
            - $ref: '#/components/schemas/CertificationExpiryDto'
        scoreDescription:
          type: string
          example: Distinction
          nullable: true
          description: Free-text score description.
        issuingAuthority:
          type: string
          example: National Safety Council
          nullable: true
          description: >-
            The authority recorded on this person's certification (may differ
            from the library entry).
        heldDirectly:
          type: boolean
          example: true
          description: Held as a direct assignment.
        careers:
          example:
            - Warehouse Operator
          description: >-
            Careers the certification is derived through (empty when held
            directly only).
          type: array
          items:
            type: string
        attachmentCount:
          type: number
          example: 2
          description: Evidence attached to the live record.
        labels:
          description: Labels on the library certification.
          type: array
          items:
            $ref: '#/components/schemas/LabelRefDto'
        submittedAt:
          type: string
          example: '2026-01-10T10:00:00.000Z'
          nullable: true
          description: When approval was last requested.
        approvedAt:
          type: string
          example: '2026-01-12T10:00:00.000Z'
          nullable: true
        deniedAt:
          type: string
          example: null
          nullable: true
        attachments:
          description: Evidence on the live record.
          type: array
          items:
            $ref: '#/components/schemas/CertificationAttachmentDto'
      required:
        - person
        - certification
        - status
        - certified
        - identifier
        - issuedAt
        - expiresAt
        - expiry
        - scoreDescription
        - issuingAuthority
        - heldDirectly
        - careers
        - attachmentCount
        - labels
        - submittedAt
        - approvedAt
        - deniedAt
        - attachments
    ProblemDetailsDto:
      type: object
      properties:
        type:
          type: string
          example: https://help.skillsdb.com/api-docs/errors/validation-error
          description: >-
            A URI identifying the problem type — always resolves to a
            documentation page.
        title:
          type: string
          example: Validation failed
        status:
          type: number
          example: 400
        detail:
          type: string
          example: limit must be between 1 and 200
          description: Human-readable explanation specific to this occurrence.
        instance:
          type: string
          example: /api/v1/people
        requestId:
          type: string
          example: 0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e
          description: >-
            Matches the X-Request-Id response header — quote it when contacting
            support.
        errors:
          description: Field-level failures, present on validation-error problems.
          type: array
          items:
            $ref: '#/components/schemas/ProblemFieldErrorDto'
        error:
          type: string
          example: trial_expired
          enum:
            - trial_expired
          description: >-
            Present only on a 403 when the workspace's trial has expired, so API
            access is suspended.
        upgrade_url:
          type: string
          example: https://app.skillsdb.com/settings/billing
          description: >-
            Present only alongside `error: trial_expired`: where a workspace
            admin upgrades to restore API access.
      required:
        - type
        - title
        - status
        - detail
        - instance
        - requestId
    PersonRefDto:
      type: object
      properties:
        id:
          type: string
          example: '1042'
        name:
          type: string
          example: Dana Whitfield
      required:
        - id
        - name
    CertificationRefDto:
      type: object
      properties:
        id:
          type: string
          example: '87'
          description: Opaque certification id (the library entry).
        name:
          type: string
          example: Forklift Operator Licence
        type:
          type: string
          example: Regulatory
          nullable: true
          description: The certification type's name.
      required:
        - id
        - name
        - type
    CertificationExpiryDto:
      type: object
      properties:
        state:
          type: string
          enum:
            - none
            - valid
            - expiring
            - expired
          example: expiring
        daysUntil:
          type: number
          example: 12
          nullable: true
          description: >-
            Whole days until expiry; negative once expired; null when there is
            no expiry date.
      required:
        - state
        - daysUntil
    LabelRefDto:
      type: object
      properties:
        id:
          type: string
          example: '12'
          description: Opaque label id.
        name:
          type: string
          example: Safety
          description: Label name.
      required:
        - id
        - name
    CertificationAttachmentDto:
      type: object
      properties:
        id:
          type: string
          example: '5011'
          description: Opaque attachment id.
        type:
          type: string
          enum:
            - file
            - link
          example: file
        label:
          type: string
          example: Certificate scan
          nullable: true
        url:
          type: string
          example: https://verify.example.org/abc
          nullable: true
          description: >-
            The link target for link attachments; null for files (use the
            download-url operation).
        createdAt:
          type: string
          example: '2026-02-01T09:00:00.000Z'
          nullable: true
      required:
        - id
        - type
        - label
        - url
        - createdAt
    ProblemFieldErrorDto:
      type: object
      properties:
        field:
          type: string
          example: email
        code:
          type: string
          example: max_length
        message:
          type: string
          example: email must be shorter than or equal to 75 characters
      required:
        - field
        - code
        - message
  securitySchemes:
    apiKey:
      scheme: bearer
      type: http
      description: >-
        An API key issued by a company administrator — an environment prefix
        (production: `sdb_live_`) followed by 32 characters. The prefix
        identifies the environment and MUST NOT be hard-coded or validated by
        clients; treat the whole key as opaque. Shown exactly once at creation;
        only a one-way hash is stored.


        **Scopes** (granted per key on the create screen; `:write` never implies
        `:read`):


        - `skills:read`

        - `skills:write`

        - `people:read`

        - `people:write`

        - `careers:read`

        - `careers:write`

        - `training:read`

        - `training:write`

        - `flags:read`

        - `flags:write`

        - `org:read`

        - `org:write`

        - `assessments:read`

        - `learning:read`

        - `learning:write`

        - `certifications:read`

        - `certifications:write`

        - `assessments:write`

        - `proficiency:read`

        - `proficiency:write`

        - `surveys:read`

        - `surveys:write`

        - `labels:read`

        - `labels:write`

````