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

# Record a tap and issue a session token

> Exchanges a tap for a session token. Send the `chip_id` and the single-use `e` value from the tap URL; the response is the token, when it expires, and what it is bound to, alongside the verified unit. Each `e` works exactly once. Accepts either an API key, when your server makes the call, or a publishable key, when the page makes it directly with no backend of your own behind it.



## OpenAPI

````yaml /openapi.json post /v1/taps
openapi: 3.1.0
info:
  title: Endstate API
  version: 0.1.0
  description: Endstate developer API for chip verification and ownership workflows.
servers:
  - url: https://api2.endstate.io
    description: Production
  - url: https://api-staging.endstate.io
    description: Staging
security: []
paths:
  /v1/taps:
    post:
      tags:
        - Taps
      summary: Record a tap and issue a session token
      description: >-
        Exchanges a tap for a session token. Send the `chip_id` and the
        single-use `e` value from the tap URL; the response is the token, when
        it expires, and what it is bound to, alongside the verified unit. Each
        `e` works exactly once. Accepts either an API key, when your server
        makes the call, or a publishable key, when the page makes it directly
        with no backend of your own behind it.
      operationId: createTap
      parameters:
        - schema:
            type: string
            minLength: 1
            maxLength: 255
            description: >-
              Unique key that makes this request safe to retry. Replaying the
              key with the same body returns the original response with
              `Idempotent-Replayed: true`; replaying it with a different body
              returns 409 `idempotency.key_conflict`.
            example: a1b2c3d4-0000-4000-8000-000000000000
          required: false
          name: Idempotency-Key
          in: header
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTapRequest'
            example:
              chip_id: ABCDEF0123
              e: C78566198547116F3A715DC1C62AF96F
      responses:
        '201':
          description: The session token, its expiry, and the verified unit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TapResponse'
              example:
                id: 12345678-1234-4567-8901-234567890abc
                session_token:
                  token: end_sess_ZW5kc3RhdGUtZXhhbXBsZS1zZXNzaW9uLXRva2VuAAA
                  expires_at: '2026-05-12T12:44:56.000Z'
                  scope:
                    chip_id: ABCDEF0123
                    unit_id: 7f8a3c10-1111-4111-8111-111111111111
                    organization_id: 99887766-aabb-4ccd-8eef-001122334455
                chip:
                  chip_id: ABCDEF0123
                  scan_count: 47
                unit:
                  id: 7f8a3c10-1111-4111-8111-111111111111
                  external_id: unit-001
                  name: Example Unit
                  attributes:
                    custom_attribute: value
                  collection:
                    id: 8e1a7f50-90ab-4cde-8012-3456789abcde
                    name: Example Collection
                    external_id: collection-001
                    contract:
                      address: '0x1111111111111111111111111111111111111111'
                      chain_id: 84532
                      status: active
                    token:
                      status: active
                      serial: 5
                redirect_url: https://brand.example/verify/ABCDEF0123
                dry_run: false
        '400':
          description: >-
            The request was malformed or failed validation.


            | Error code | When |

            | --- | --- |

            | `validation.failed` | The request failed schema validation. See
            `error.details` for per-field issues. |
          x-error-codes:
            - validation.failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation.failed:
                  summary: >-
                    The request failed schema validation. See `error.details`
                    for per-field issues.
                  value:
                    error:
                      code: validation.failed
                      message: >-
                        The request failed schema validation. See
                        `error.details` for per-field issues.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/validation-failed
        '401':
          description: |-
            Authentication failed.

            | Error code | When |
            | --- | --- |
            | `auth.unauthorized` | Credential is missing or malformed. |
          x-error-codes:
            - auth.unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                auth.unauthorized:
                  summary: Credential is missing or malformed.
                  value:
                    error:
                      code: auth.unauthorized
                      message: Credential is missing or malformed.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/auth-unauthorized
        '403':
          description: >-
            Authenticated, but not permitted.


            | Error code | When |

            | --- | --- |

            | `auth.forbidden` | Credential is valid but does not have access to
            the requested resource or action. |
          x-error-codes:
            - auth.forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                auth.forbidden:
                  summary: >-
                    Credential is valid but does not have access to the
                    requested resource or action.
                  value:
                    error:
                      code: auth.forbidden
                      message: >-
                        Credential is valid but does not have access to the
                        requested resource or action.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/auth-forbidden
        '404':
          description: >-
            The resource does not exist within your organization.


            | Error code | When |

            | --- | --- |

            | `chip.not_found` | The chip ID does not exist in your
            organization. |

            | `unit.not_found` | The unit ID does not exist in your
            organization. |
          x-error-codes:
            - chip.not_found
            - unit.not_found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                chip.not_found:
                  summary: The chip ID does not exist in your organization.
                  value:
                    error:
                      code: chip.not_found
                      message: The chip ID does not exist in your organization.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/chip-not-found
                unit.not_found:
                  summary: The unit ID does not exist in your organization.
                  value:
                    error:
                      code: unit.not_found
                      message: The unit ID does not exist in your organization.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/unit-not-found
        '409':
          description: >-
            The request conflicts with the resource's current state.


            | Error code | When |

            | --- | --- |

            | `idempotency.in_progress` | A request with this `Idempotency-Key`
            is already in progress. |

            | `idempotency.key_conflict` | The `Idempotency-Key` was previously
            used with a different request body. |
          x-error-codes:
            - idempotency.in_progress
            - idempotency.key_conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                idempotency.in_progress:
                  summary: >-
                    A request with this `Idempotency-Key` is already in
                    progress.
                  value:
                    error:
                      code: idempotency.in_progress
                      message: >-
                        A request with this `Idempotency-Key` is already in
                        progress.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/idempotency-in-progress
                idempotency.key_conflict:
                  summary: >-
                    The `Idempotency-Key` was previously used with a different
                    request body.
                  value:
                    error:
                      code: idempotency.key_conflict
                      message: >-
                        The `Idempotency-Key` was previously used with a
                        different request body.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/idempotency-key-conflict
        '410':
          description: >-
            The credential has already been consumed.


            | Error code | When |

            | --- | --- |

            | `chip.already_scanned` | The tap credential has already been used.
            Each tap produces a single-use credential. |
          x-error-codes:
            - chip.already_scanned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                chip.already_scanned:
                  summary: >-
                    The tap credential has already been used. Each tap produces
                    a single-use credential.
                  value:
                    error:
                      code: chip.already_scanned
                      message: >-
                        The tap credential has already been used. Each tap
                        produces a single-use credential.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/chip-already-scanned
        '422':
          description: >-
            The request was well-formed but could not be processed.


            | Error code | When |

            | --- | --- |

            | `chip.invalid_e_value` | The tap credential (`e` value) was
            rejected as invalid. |
          x-error-codes:
            - chip.invalid_e_value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                chip.invalid_e_value:
                  summary: The tap credential (`e` value) was rejected as invalid.
                  value:
                    error:
                      code: chip.invalid_e_value
                      message: The tap credential (`e` value) was rejected as invalid.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/chip-invalid-e-value
        '429':
          description: |-
            Too many requests.

            | Error code | When |
            | --- | --- |
            | `rate_limit.exceeded` | Per-key rate limit exceeded. |
          x-error-codes:
            - rate_limit.exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                rate_limit.exceeded:
                  summary: Per-key rate limit exceeded.
                  value:
                    error:
                      code: rate_limit.exceeded
                      message: Per-key rate limit exceeded.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/rate-limit-exceeded
        '500':
          description: >-
            Something went wrong on our end.


            | Error code | When |

            | --- | --- |

            | `internal.error` | An unexpected server error occurred. Retry with
            exponential backoff and include `request_id` in any support request.
            |
          x-error-codes:
            - internal.error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                internal.error:
                  summary: >-
                    An unexpected server error occurred. Retry with exponential
                    backoff and include `request_id` in any support request.
                  value:
                    error:
                      code: internal.error
                      message: >-
                        An unexpected server error occurred. Retry with
                        exponential backoff and include `request_id` in any
                        support request.
                      request_id: req_8e1a7f50-90ab-4cde-f012-3456789abcde
                      doc_url: https://docs.endstate.io/errors/internal-error
      security:
        - ApiKeyBearer: []
        - PublishableKeyBearer: []
components:
  schemas:
    CreateTapRequest:
      type: object
      properties:
        chip_id:
          type: string
          pattern: ^[0-9A-Fa-f]{10}$
          description: Chip identifier from the tap URL.
          example: ABCDEF0123
        e:
          type: string
          pattern: ^[0-9A-Fa-f]{32}$
          description: The single-use value from the tap URL. Each one works exactly once.
          example: C78566198547116F3A715DC1C62AF96F
        c:
          type: string
          pattern: ^[0-9A-F]{16}$
          description: >-
            Tap verification code (`c`) from the tap URL. Include it whenever
            the tag provides one.
          example: 940E8AA6628759B3
        ttl:
          type:
            - integer
            - 'null'
          minimum: 60
          maximum: 3600
          description: >-
            Session-token TTL override in seconds, between 60 and 3600
            inclusive. Secret key only: a publishable-key caller does not choose
            how long its own credential lives.
          example: 600
        dry_run:
          type: boolean
          description: >-
            Validate the tap without recording it or issuing a session token.
            Requires an admin secret key.
          example: false
      required:
        - chip_id
        - e
      additionalProperties: false
      example:
        chip_id: ABCDEF0123
        e: C78566198547116F3A715DC1C62AF96F
    TapResponse:
      type: object
      properties:
        id:
          type:
            - string
            - 'null'
          format: uuid
          description: >-
            Tap identifier. null when dry_run is true, because no tap is
            recorded.
          example: 12345678-1234-4567-8901-234567890abc
        session_token:
          $ref: '#/components/schemas/TapSessionToken'
        chip:
          type: object
          properties:
            chip_id:
              type: string
              pattern: ^[0-9A-F]{10}$
              example: ABCDEF0123
            scan_count:
              type: integer
              minimum: 0
          required:
            - chip_id
            - scan_count
        unit:
          type: object
          properties:
            id:
              type: string
              format: uuid
            external_id:
              type:
                - string
                - 'null'
            name:
              type:
                - string
                - 'null'
            attributes:
              type:
                - object
                - 'null'
              additionalProperties: {}
            collection:
              allOf:
                - $ref: '#/components/schemas/CollectionMembership'
                - description: >-
                    The unit's collection and issuance status. Null only for
                    units created before collections existed.
          required:
            - id
            - external_id
            - name
            - attributes
            - collection
        redirect_url:
          type:
            - string
            - 'null'
          format: uri
          description: >-
            Canonical verify URL on the org's verified domain. null when no
            verified domain exists.
          example: https://brand.example/verify/ABCDEF0123
        dry_run:
          type: boolean
      required:
        - id
        - session_token
        - chip
        - unit
        - redirect_url
        - dry_run
      example:
        id: 12345678-1234-4567-8901-234567890abc
        session_token:
          token: end_sess_ZW5kc3RhdGUtZXhhbXBsZS1zZXNzaW9uLXRva2VuAAA
          expires_at: '2026-05-12T12:44:56.000Z'
          scope:
            chip_id: ABCDEF0123
            unit_id: 7f8a3c10-1111-4111-8111-111111111111
            organization_id: 99887766-aabb-4ccd-8eef-001122334455
        chip:
          chip_id: ABCDEF0123
          scan_count: 47
        unit:
          id: 7f8a3c10-1111-4111-8111-111111111111
          external_id: unit-001
          name: Example Unit
          attributes:
            custom_attribute: value
          collection:
            id: 8e1a7f50-90ab-4cde-8012-3456789abcde
            name: Example Collection
            external_id: collection-001
            contract:
              address: '0x1111111111111111111111111111111111111111'
              chain_id: 84532
              status: active
            token:
              status: active
              serial: 5
        redirect_url: https://brand.example/verify/ABCDEF0123
        dry_run: false
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              anyOf:
                - $ref: '#/components/schemas/ErrorCode'
                - type: string
              description: >-
                Stable, namespaced error code. The `ErrorCode` catalogue lists
                every code defined today; handle an unrecognized value as a
                generic failure rather than throwing, since codes are added over
                time.
              example: chip.not_found
            message:
              type: string
              description: >-
                Human-readable description, for logs and debugging. Wording may
                change without notice - never parse or match against it.
              example: Chip not found
            request_id:
              type: string
              description: >-
                Identifier for this request, matching the `X-Request-Id`
                response header. Log it and include it in any support request.
              example: req_8e1a7f50-90ab-4cde-f012-3456789abcde
            doc_url:
              type: string
              description: Documentation page for this error code.
              example: https://docs.endstate.io/errors/chip-not-found
            details:
              $ref: '#/components/schemas/ValidationErrorDetails'
          required:
            - code
            - message
            - request_id
            - doc_url
      required:
        - error
      description: >-
        Every error response, regardless of endpoint or HTTP status, uses this
        envelope.
    TapSessionToken:
      type:
        - object
        - 'null'
      properties:
        token:
          type: string
          example: end_sess_ZW5kc3RhdGUtZXhhbXBsZS1zZXNzaW9uLXRva2VuAAA
        expires_at:
          type: string
          format: date-time
          example: '2026-05-12T12:44:56.000Z'
        scope:
          type: object
          properties:
            chip_id:
              type: string
              pattern: ^[0-9A-F]{10}$
              example: ABCDEF0123
            unit_id:
              type: string
              format: uuid
              example: 7f8a3c10-1111-4111-8111-111111111111
            organization_id:
              type: string
              format: uuid
              example: 99887766-aabb-4ccd-8eef-001122334455
          required:
            - chip_id
            - unit_id
            - organization_id
      required:
        - token
        - expires_at
        - scope
      description: >-
        Opaque session token scoped to this organization, chip, unit, and tap.
        null on dry_run.
    CollectionMembership:
      type:
        - object
        - 'null'
      properties:
        id:
          type: string
          format: uuid
        name:
          type:
            - string
            - 'null'
        external_id:
          type:
            - string
            - 'null'
        contract:
          type: object
          properties:
            address:
              type: string
              description: Unique address identifying the collection.
              example: '0x1111111111111111111111111111111111111111'
            chain_id:
              type: integer
              example: 84532
            status:
              type: string
              enum:
                - deploying
                - active
                - failed
              description: >-
                Collection lifecycle status. `deploying` while the collection is
                being provisioned; `active` once it is ready to use.
              example: active
          required:
            - address
            - chain_id
            - status
        token:
          $ref: '#/components/schemas/UnitToken'
      required:
        - id
        - name
        - external_id
        - contract
        - token
      description: >-
        The unit's collection. Null only for units created before collections
        existed.
    ErrorCode:
      type: string
      enum:
        - validation.failed
        - auth.unauthorized
        - auth.forbidden
        - session_token.invalid_or_expired
        - session_token.wrong_chip
        - not_found.resource
        - rate_limit.exceeded
        - chip.not_found
        - chip.invalid_e_value
        - chip.already_scanned
        - quota.exceeded
        - chip.not_a_test_chip
        - unit.not_found
        - unit.already_exists
        - unit.not_minted
        - collection.not_found
        - collection.already_exists
        - collection.not_active
        - chip.already_paired
        - chip.bulk_mixed_collections
        - chip.bulk_pending
        - unit.issuance_pending
        - claim.owner_unknown
        - claim.already_to_recipient
        - claim.in_progress
        - claim.not_found
        - chip_replacement.locked
        - chip_replacement.in_progress
        - chip_replacement.not_found
        - transfer.owner_unknown
        - transfer.already_to_recipient
        - transfer.in_progress
        - transfer.not_found
        - idempotency.key_conflict
        - idempotency.in_progress
        - internal.error
      description: >-
        Stable, namespaced error code in `<resource>.<reason>` form. Branch on
        this rather than on `message` or the HTTP status. This is the catalogue
        as of this spec revision, not a closed set - see
        `ErrorResponse.error.code`.
      example: chip.not_found
    ValidationErrorDetails:
      type: object
      properties:
        formErrors:
          type: array
          items:
            type: string
          description: Issues that apply to the request as a whole rather than one field.
          example: []
        fieldErrors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Validation issues keyed by the field that failed.
          example:
            name:
              - Required
      required:
        - formErrors
        - fieldErrors
      description: Per-field validation detail. Present only on `validation.failed`.
    UnitToken:
      type: object
      properties:
        status:
          type: string
          enum:
            - pending
            - active
          description: >-
            Issuance state of this unit within the collection. `pending` until
            the unit is issued; `active` once issued. There is no failure
            status: a unit whose issuance cannot complete stays `pending` - if a
            unit remains `pending` well beyond a few minutes, stop polling and
            contact support.
          example: pending
        serial:
          type:
            - integer
            - 'null'
          description: >-
            Collection-scoped serial number. Assigned when the unit becomes
            `active`; null until then.
          example: 5
      required:
        - status
        - serial
  securitySchemes:
    ApiKeyBearer:
      type: http
      scheme: bearer
      bearerFormat: end_sk
      description: >-
        Use `Authorization: Bearer end_sk_*` for partner API keys (e.g.
        `end_sk_AbCd_example_api_key`).
    PublishableKeyBearer:
      type: http
      scheme: bearer
      bearerFormat: end_pk
      description: >-
        Use `Authorization: Bearer end_pk_live_*` for V2 publishable keys. Safe
        to use from a browser: a publishable key identifies your organization
        and grants no access by itself.

````