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

# List purchases

> Every sale in the event. Meant for dumping into a data warehouse: walk it with the cursor and use `since` over `occurred_at` for incremental passes.



## OpenAPI

````yaml /openapi.en.json get /transactions
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:
  /transactions:
    get:
      tags:
        - Cashless
      summary: List purchases
      description: >-
        Every sale in the event. Meant for dumping into a data warehouse: walk
        it with the cursor and use `since` over `occurred_at` for incremental
        passes.
      parameters:
        - schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
          in: query
          name: limit
          required: false
          description: Number of items per page.
        - schema:
            type: string
          in: query
          name: starting_after
          required: false
          description: >-
            Cursor returned as `next_cursor` by the previous page. Omit it on
            the first call.
        - schema:
            type: string
          in: query
          name: day
          required: false
          description: Day id from `GET /days`.
        - schema:
            type: string
          in: query
          name: since
          required: false
          description: >-
            ISO 8601 date. Filters by `occurred_at`, that is, when the operation
            actually happened.
      responses:
        '200':
          description: The event's purchases.
          content:
            application/json:
              schema:
                type: object
                description: The event's purchases.
                properties:
                  object:
                    type: string
                    enum:
                      - list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Transaction'
                  has_more:
                    type: boolean
                    description: If `true`, request the next page with `starting_after`.
                  next_cursor:
                    type:
                      - 'null'
                      - string
                    description: Value to pass in `starting_after` to continue.
                required:
                  - object
                  - data
                  - has_more
                  - next_cursor
        '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'
        '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:
    Transaction:
      type: object
      properties:
        object:
          type: string
          enum:
            - transaction
        id:
          type: string
        status:
          type: string
          enum:
            - pending
            - completed
            - failed
            - refunded
        type:
          type: string
          enum:
            - cart
            - custom
        payment_method:
          type: string
          enum:
            - wristband
            - card
        total:
          type: integer
          description: Total charged, in cents.
        total_base:
          type: integer
        total_tax:
          type: integer
        tax_breakdown:
          type: array
          items:
            type: object
            properties:
              tax_type:
                type: string
                enum:
                  - IVA
                  - IGIC
              rate:
                type: number
              base:
                type: integer
              amount:
                type: integer
        items:
          type: array
          items:
            type: object
            properties:
              product_id:
                type:
                  - 'null'
                  - string
              name:
                type:
                  - 'null'
                  - string
              quantity:
                type: integer
              unit_price:
                type: integer
              charged_amount:
                type: integer
                description: >-
                  What was actually charged to the balance. Differs from
                  `unit_price * quantity` when part was covered by included
                  items.
              included_quantity:
                type: integer
        wristband_id:
          type:
            - 'null'
            - string
        day_id:
          type:
            - 'null'
            - string
        device_id:
          type:
            - 'null'
            - string
        occurred_at:
          type:
            - 'null'
            - string
          description: >-
            When it actually happened. An offline sale carries its real time
            here, not the sync time: this is the field to group by.
        created_at:
          type:
            - 'null'
            - string
    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.

````