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

> ## Agent Instructions
> Documentação das APIs da DeliveryVip, em português. Regras que mais quebram integrações: (1) Merchant API: faça polling em GET /merchant/v3/events:polling no máximo a cada 30 s por token; confirme todo evento recebido com POST /merchant/v3/events/acknowledgment e corpo [{"id": "<eventId>"}] (o campo lido é id, não eventId), inclusive eventos repetidos ou que o sistema não trata; deduplique por eventId; o evento é só um aviso, busque o pedido na orderURL. (2) Tokens client_credentials valem 24 h e não têm refresh_token: peça outro ao receber 401. (3) Webhooks: valide a assinatura HMAC sobre o corpo bruto e responda 2xx rápido; o polling é a reconciliação. (4) Open Delivery: host od.deliveryvip.com.br, um client_secret por loja, datas em UTC. (5) Fidelidade: o operador implementa 5 rotas assinadas com HMAC e chama o config push; o estorno precisa ser idempotente. (6) Partner API: scope dv.partner. Não invente endpoints: use a referência gerada das specs OpenAPI.

# Synchronous balance debit

> The authoritative debit, called as the LAST step inside DeliveryVip's order-creation transaction. A non-201 answer (or timeout) rolls the whole order back — the order is never born and the customer keeps shopping. Idempotency key is relatedOrderId (the order UUID): replaying the same relatedOrderId with the same amount must answer 201 with the SAME redemptionId; the same relatedOrderId with a DIFFERENT amount must be rejected with 422 code idempotency_conflict. amount is a JSON number (natural serialization, e.g. 25.0) and is always the value actually discounted on the order — it may be lower than what the customer originally reserved if the cart shrank. It also already honours the operator's own capabilities.redemptionLimits: never below the declared minimum, never above the declared maximum. Percentage ceilings are computed over the order's items (subtotal minus other cart discounts) — shipping and fees are never redeemable — so for operators whose own rule applies to the order total this amount can be lower than their ceiling, never higher. A 201 without redemptionId is treated as malformed and rolls the order back.



## OpenAPI

````yaml https://app.deliveryvip.com.br/swaggers/loyalty_operator.yaml post /v1/loyalty/redemptions
openapi: 3.0.3
info:
  version: v1
  title: DeliveryVip Loyalty Operator Contract
  description: >-
    The HTTP contract a loyalty operator must implement to serve the DeliveryVip
    loyalty extension. DeliveryVip is the ordering application: it CALLS these
    endpoints on the api_base_url declared by the operator's app (loyalty
    extension, dv.loyalty scope). Every request is signed with HMAC-SHA256 of
    the RAW body (empty string for body-less requests) using the app's OAuth
    secret — verify the signature against the exact bytes received, never a
    re-serialization. Respond fast: DeliveryVip uses 3s connect / 5s read
    timeouts, and 5 transport failures (timeout/5xx) within 5 minutes open a
    circuit breaker for the whole extension. Business answers (409/422/403/404)
    never count as failures. Configuration is pushed the other way (operator ->
    DeliveryVip) via PUT/DELETE /merchant/v3/{merchantId}/loyalty/config — see
    the Merchant API swagger; the PUT triggers a synchronous signed GET
    /v1/loyalty/ping here before anything is stored. That push is also where an
    operator declares its commercial boundaries per order
    (capabilities.redemptionLimits: an optional min and max, each fixed BRL or
    percent of the redeemable base). DeliveryVip enforces them before calling
    this API, so the amount below always already respects whatever the operator
    declared. Canonical byte-pinned request/response examples live in the
    platform repository under test/fixtures/files/loyalty/.
servers:
  - url: '{api_base_url}'
    description: >-
      The base URL the operator declares on the loyalty extension (https only;
      may include a path prefix).
    variables:
      api_base_url:
        default: https://operator.example.com/dv/v1
security:
  - appId: []
    appMerchantId: []
    appSignature: []
tags:
  - name: Handshake
    description: Synchronous verification during the config push.
  - name: Accounts
    description: >-
      Customer identity and balance. The account id must be opaque and stable
      across relinks.
  - name: Redemptions
    description: Synchronous balance debit (checkout) and its explicit idempotent reversal.
paths:
  /v1/loyalty/redemptions:
    post:
      tags:
        - Redemptions
      summary: Synchronous balance debit
      description: >-
        The authoritative debit, called as the LAST step inside DeliveryVip's
        order-creation transaction. A non-201 answer (or timeout) rolls the
        whole order back — the order is never born and the customer keeps
        shopping. Idempotency key is relatedOrderId (the order UUID): replaying
        the same relatedOrderId with the same amount must answer 201 with the
        SAME redemptionId; the same relatedOrderId with a DIFFERENT amount must
        be rejected with 422 code idempotency_conflict. amount is a JSON number
        (natural serialization, e.g. 25.0) and is always the value actually
        discounted on the order — it may be lower than what the customer
        originally reserved if the cart shrank. It also already honours the
        operator's own capabilities.redemptionLimits: never below the declared
        minimum, never above the declared maximum. Percentage ceilings are
        computed over the order's items (subtotal minus other cart discounts) —
        shipping and fees are never redeemable — so for operators whose own rule
        applies to the order total this amount can be lower than their ceiling,
        never higher. A 201 without redemptionId is treated as malformed and
        rolls the order back.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/redemptionRequest'
      responses:
        '201':
          description: Balance debited.
          content:
            application/json:
              schema:
                type: object
                required:
                  - redemptionId
                properties:
                  redemptionId:
                    type: string
                    description: >-
                      The operator's identifier for this debit (needed for
                      reversal).
        '409':
          description: >-
            Insufficient balance. The order is rolled back and the customer
            keeps shopping.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/errorWithCode'
        '422':
          description: >-
            Business rejection (e.g. below_minimum_order, idempotency_conflict).
            The order is rolled back with a generic message to the customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/errorWithCode'
components:
  schemas:
    redemptionRequest:
      type: object
      required:
        - accountId
        - relatedOrderId
        - kind
        - amount
        - order
      properties:
        accountId:
          type: string
        relatedOrderId:
          type: string
          format: uuid
          description: >-
            The DeliveryVip order UUID — the idempotency key (one debit per
            order).
        kind:
          type: string
          enum:
            - balance
        amount:
          type: number
          description: Value to debit, up to 2 decimal places, as a JSON number.
        order:
          type: object
          required:
            - subtotal
            - total
            - items
          properties:
            subtotal:
              type: number
            total:
              type: number
            items:
              type: array
              items:
                type: object
                required:
                  - name
                  - quantity
                  - totalPrice
                properties:
                  name:
                    type: string
                  externalCode:
                    type: string
                    description: >-
                      The merchant's PDV/ERP code for the item; omitted when the
                      product has none.
                  quantity:
                    type: integer
                  totalPrice:
                    type: number
    errorWithCode:
      type: object
      properties:
        code:
          type: string
          description: >-
            Machine-readable reason (e.g. insufficient_balance,
            below_minimum_order, idempotency_conflict).
  securitySchemes:
    appId:
      type: apiKey
      in: header
      name: X-App-Id
      description: The uid of the operator's OAuth application on DeliveryVip.
    appMerchantId:
      type: apiKey
      in: header
      name: X-App-MerchantId
      description: The UUID of the merchant (company) the call concerns.
    appSignature:
      type: apiKey
      in: header
      name: X-App-Signature
      description: >-
        Hex HMAC-SHA256 of the raw request body (empty string when there is no
        body), keyed with the app's OAuth secret. Verify against the exact bytes
        received.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.