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

# Accredit a ticket

> Redeems the ticket and links the scanned wristband to it. This is the door operation: the operator reads the QR and then the wristband.

The wristband inherits the zones of the ticket type plus the ticket's own extra zones, and its valid days.

Requires `Idempotency-Key`. Repeating the call with the same key returns the wristband already created, so a retry after a network drop does not issue a second one.

If another door accredited the same ticket first, the response is `409 ticket_already_claimed` and this call's wristband is left **under review**: it can be scanned so security is alerted, but it cannot spend. A supervisor resolves it from the dashboard.



## OpenAPI

````yaml /openapi.en.json post /tickets/{code}/check-in
openapi: 3.1.0
info:
  title: VENTRY API
  version: 1.0.0
  description: >-
    **VENTRY**'s public API for external integrators.


    It lets you push tickets from a ticketing platform, accredit attendees from
    your

    own application and read the state of the event in real time.


    ## Authentication


    Every call carries an API key in the `Authorization` header:


    ```

    Authorization: Bearer vk_live_8Kq2...

    ```


    The key is issued from the VENTRY dashboard and **shown only once**. If it
    is

    lost, a new one must be issued. Keys requesting write scopes are created
    disabled

    and need an event administrator to enable them by hand.


    ## One deployment, one event


    Each VENTRY installation corresponds to **a single event**. The API key
    already

    determines which event you are talking about, which is why no endpoint takes
    an

    event identifier. A key may additionally be restricted to certain ticket
    types or

    certain days: anything outside that scope behaves as if it did not exist.


    ## Conventions


    - **Amounts**: integers in **cents**. `1250` is €12.50. The currency is in
    `GET /event`.

    - **Dates**: ISO 8601 in UTC. The event's time zone is in `GET /event`.

    - **Identifiers**: 24-character hexadecimal strings.

    - **Fields**: `snake_case`.


    ### `occurred_at` versus `created_at`


    VENTRY terminals keep charging without connectivity and sync afterwards.
    That is

    why sales and top-ups carry two timestamps: `occurred_at` is when it
    actually

    happened and `created_at` when it reached the server. **To group by time or

    reconcile takings you must always use `occurred_at`**; a sale made at 22:00
    and

    synced at 02:00 belongs to Friday night, not Saturday's.


    ## Pagination


    Lists return `{ object: "list", data, has_more, next_cursor }`. To get the
    next

    page, repeat the call with `starting_after=<next_cursor>`. There are no page

    numbers: the cursor stays stable even while records are being created as you

    walk the list.


    To sync incrementally, combine `updated_since` with the cursor.


    ## Idempotency


    Writes require the `Idempotency-Key` header with a UUID generated by the
    client.

    Repeating the same call with the same key returns the original response
    without

    executing anything again, so a retry after a network failure never
    duplicates a

    ticket or a top-up. Reusing the key with a different body returns

    `422 idempotency_key_reused`.


    ## Limits


    By default **120 requests per minute** per key, and **20 per minute** for
    writes.

    Every response carries `x-ratelimit-remaining` and, once exhausted,

    `retry-after` with the seconds left.


    ## Errors


    Every error shares the same shape and carries a `request_id` worth logging:
    with

    it, VENTRY support can find your exact request.
  contact:
    name: Soporte VENTRY
    url: https://ventry.es
servers:
  - url: http://localhost:3000/v1
    description: Event server
security:
  - apiKey: []
tags:
  - name: Event
    description: >-
      Event configuration: days, zones and ticket types. Read this first to map
      your own catalogue against VENTRY's.
  - name: Tickets
    description: Create, read and cancel tickets. The API's main use case.
  - name: Access control
    description: >-
      Door accreditation and zone control. Requires write scopes activated
      manually.
  - name: Cashless
    description: Wristband balances, purchases and top-ups. All amounts in cents.
  - name: Reports
    description: Sales aggregates, consistent with the dashboard's daily closeout.
  - name: Meta
    description: Credential checks and documentation.
paths:
  /tickets/{code}/check-in:
    post:
      tags:
        - Access control
      summary: Accredit a ticket
      description: >-
        Redeems the ticket and links the scanned wristband to it. This is the
        door operation: the operator reads the QR and then the wristband.


        The wristband inherits the zones of the ticket type plus the ticket's
        own extra zones, and its valid days.


        Requires `Idempotency-Key`. Repeating the call with the same key returns
        the wristband already created, so a retry after a network drop does not
        issue a second one.


        If another door accredited the same ticket first, the response is `409
        ticket_already_claimed` and this call's wristband is left **under
        review**: it can be scanned so security is alerted, but it cannot spend.
        A supervisor resolves it from the dashboard.
      parameters:
        - schema:
            type: string
          in: path
          name: code
          required: true
        - schema:
            type: string
          in: header
          name: idempotency-key
          required: true
          description: >-
            Unique identifier for the scan. Retrying with the same value is
            safe.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - wristband
              properties:
                wristband:
                  type: object
                  required:
                    - nfc
                  properties:
                    nfc:
                      type: string
                      minLength: 4
                      maxLength: 64
                      description: NFC identifier read from the chip.
                    uhf:
                      type: string
                      maxLength: 64
                      description: >-
                        UHF identifier, if the wristband has one. If omitted,
                        the NFC is reused.
                originality_signature:
                  type: string
                  maxLength: 512
                  description: >-
                    Chip originality signature, in hexadecimal. Stored even if
                    it fails to verify, so it can be audited later.
                originality_verified:
                  type: boolean
                  description: >-
                    Whether that signature verified against the manufacturer's
                    public key.
      responses:
        '200':
          description: Result of the accreditation.
          content:
            application/json:
              schema:
                type: object
                description: Result of the accreditation.
                properties:
                  object:
                    type: string
                    enum:
                      - checkin_result
                  ticket_code:
                    type: string
                  wristband:
                    type: object
                    properties:
                      id:
                        type: string
                      nfc:
                        type: string
                      uhf:
                        type: string
                      status:
                        type: string
        '400':
          description: Malformed request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing, invalid or expired API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The API key lacks the required scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: The resource does not exist, or is outside the API key's scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            The ticket was already accredited elsewhere, or the wristband is
            already in use.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: The accreditation could not be completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      description: >-
        Every API error shares this shape. Branch on `code`, which is stable;
        `message` may be reworded.
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              enum:
                - authentication_error
                - permission_error
                - invalid_request_error
                - not_found_error
                - conflict_error
                - rate_limit_error
                - api_error
            code:
              type: string
              example: ticket_not_found
            message:
              type: string
            param:
              type: string
              description: Request field that caused the error, where applicable.
            request_id:
              type: string
              description: Request identifier. Quote it when contacting support.
              example: req_9f2c1a4e7b304d51
          required:
            - type
            - code
            - message
            - request_id
      required:
        - error
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Integrator API key, issued from the VENTRY dashboard. Format
        `vk_live_...` in production and `vk_test_...` in test environments.

````