> ## 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.

# Acreditar una entrada

> Canjea la entrada y le vincula la pulsera escaneada. Es la operación de puerta: el operario lee el QR y después la pulsera.

La pulsera hereda las zonas del tipo de entrada más las zonas extra de la propia entrada, y sus días de validez.

Exige `Idempotency-Key`. Repetir la llamada con la misma clave devuelve la pulsera ya creada, así que un reintento tras un corte de red no emite una segunda pulsera.

Si otra puerta acreditó la misma entrada primero, la respuesta es `409 ticket_already_claimed` y la pulsera de esta llamada queda **en revisión**: se puede escanear para que seguridad avise, pero no gastar. La resuelve un supervisor desde el panel.



## OpenAPI

````yaml /openapi.json post /tickets/{code}/check-in
openapi: 3.1.0
info:
  title: VENTRY API
  version: 1.0.0
  description: >-
    API pública de **VENTRY** para integradores externos.


    Permite volcar entradas desde una plataforma de ticketing, acreditar
    asistentes

    desde una aplicación propia y consultar el estado del evento en tiempo real.


    ## Autenticación


    Todas las llamadas viajan con una API key en la cabecera `Authorization`:


    ```

    Authorization: Bearer vk_live_8Kq2...

    ```


    La clave se genera desde el panel de VENTRY y **se muestra una sola vez**.
    Si se

    pierde, hay que emitir otra. Las claves que piden permisos de escritura
    nacen

    desactivadas y necesitan que un administrador del evento las habilite a
    mano.


    ## Un despliegue, un evento


    Cada instalación de VENTRY corresponde a **un único evento**. La API key ya

    determina de qué evento se está hablando, por eso ningún endpoint pide un

    identificador de evento. Una clave puede además estar limitada a ciertos
    tipos

    de entrada o a ciertos días: lo que quede fuera de ese ámbito se comporta
    como

    si no existiera.


    ## Convenciones


    - **Importes**: enteros en **céntimos**. `1250` son 12,50 €. La moneda está
    en `GET /event`.

    - **Fechas**: ISO 8601 en UTC. El huso horario del evento está en `GET
    /event`.

    - **Identificadores**: cadenas de 24 caracteres hexadecimales.

    - **Campos**: `snake_case`.


    ### `occurred_at` frente a `created_at`


    Los terminales de VENTRY siguen cobrando sin cobertura y sincronizan
    después.

    Por eso las ventas y recargas llevan dos marcas: `occurred_at` es cuándo
    pasó

    de verdad y `created_at` cuándo llegó al servidor. **Para agrupar por tiempo
    o

    cuadrar caja hay que usar siempre `occurred_at`**; una venta de las 22:00

    sincronizada a las 02:00 pertenece a la noche del viernes, no a la del
    sábado.


    ## Paginación


    Los listados devuelven `{ object: "list", data, has_more, next_cursor }`.
    Para

    la siguiente página se repite la llamada con `starting_after=<next_cursor>`.

    No hay números de página: el cursor es estable aunque se sigan creando
    registros

    mientras se recorre el listado.


    Para sincronizar de forma incremental, combina `updated_since` con el
    cursor.


    ## Idempotencia


    Las escrituras exigen la cabecera `Idempotency-Key` con un UUID generado por

    el cliente. Repetir la misma llamada con la misma clave devuelve la
    respuesta

    original sin volver a ejecutar nada, así que un reintento tras un fallo de
    red

    nunca duplica una entrada ni una recarga. Reutilizar la clave con un cuerpo

    distinto devuelve `422 idempotency_key_reused`.


    ## Límites


    Por defecto **120 peticiones por minuto** y clave, y **20 por minuto** para
    las

    escrituras. Cada respuesta lleva `x-ratelimit-remaining` y, al agotarse,

    `retry-after` con los segundos que faltan.


    ## Errores


    Todos los errores comparten la misma forma y llevan un `request_id` que

    conviene registrar: con él, el soporte de VENTRY encuentra la petición
    exacta.
  contact:
    name: Soporte VENTRY
    url: https://ventry.es
servers:
  - url: http://localhost:3000/v1
    description: Servidor del evento
security:
  - apiKey: []
tags:
  - name: Evento
    description: >-
      Configuración del evento: días, zonas y tipos de entrada. Es lo primero
      que hay que leer para mapear el catálogo propio contra el de VENTRY.
  - name: Entradas
    description: >-
      Alta, consulta y anulación de entradas. El caso de uso principal de la
      API.
  - name: Accesos
    description: >-
      Acreditación en puerta y control de zonas. Requiere permisos de escritura
      activados manualmente.
  - name: Cashless
    description: >-
      Saldo de pulseras, consumiciones y recargas. Todos los importes en
      céntimos.
  - name: Informes
    description: Agregados de venta, coherentes con el cierre de caja del panel.
  - name: Meta
    description: Comprobación de credenciales y documentación.
paths:
  /tickets/{code}/check-in:
    post:
      tags:
        - Accesos
      summary: Acreditar una entrada
      description: >-
        Canjea la entrada y le vincula la pulsera escaneada. Es la operación de
        puerta: el operario lee el QR y después la pulsera.


        La pulsera hereda las zonas del tipo de entrada más las zonas extra de
        la propia entrada, y sus días de validez.


        Exige `Idempotency-Key`. Repetir la llamada con la misma clave devuelve
        la pulsera ya creada, así que un reintento tras un corte de red no emite
        una segunda pulsera.


        Si otra puerta acreditó la misma entrada primero, la respuesta es `409
        ticket_already_claimed` y la pulsera de esta llamada queda **en
        revisión**: se puede escanear para que seguridad avise, pero no gastar.
        La resuelve un supervisor desde el panel.
      parameters:
        - schema:
            type: string
          in: path
          name: code
          required: true
        - schema:
            type: string
          in: header
          name: idempotency-key
          required: true
          description: >-
            Identificador único del escaneo. Reintentar con el mismo valor es
            seguro.
      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: Identificador NFC leído del chip.
                    uhf:
                      type: string
                      maxLength: 64
                      description: >-
                        Identificador UHF, si la pulsera lo tiene. Si se omite
                        se reutiliza el NFC.
                originality_signature:
                  type: string
                  maxLength: 512
                  description: >-
                    Firma de originalidad del chip, en hexadecimal. Se guarda
                    aunque no verifique, para poder auditar después qué pulseras
                    entraron.
                originality_verified:
                  type: boolean
                  description: >-
                    Si esa firma verificó contra la clave pública del
                    fabricante.
      responses:
        '200':
          description: Resultado de la acreditación.
          content:
            application/json:
              schema:
                type: object
                description: Resultado de la acreditación.
                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: Petición mal formada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: API key ausente, inválida o caducada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: La API key no tiene el permiso necesario.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: El recurso no existe o está fuera del ámbito de la API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            La entrada ya se acreditó en otro punto, o la pulsera ya está en
            uso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: No se pudo completar la acreditación.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Se ha superado el límite de peticiones.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Error interno.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      description: >-
        Todos los errores de la API comparten esta forma. Ramifica sobre `code`,
        que es estable; `message` puede cambiar de redacción.
      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: Campo de la petición que provocó el error, si aplica.
            request_id:
              type: string
              description: Identificador de la petición. Cítalo al contactar con soporte.
              example: req_9f2c1a4e7b304d51
          required:
            - type
            - code
            - message
            - request_id
      required:
        - error
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        API key del integrador, emitida desde el panel de VENTRY. Formato
        `vk_live_...` en producción y `vk_test_...` en entornos de prueba.

````