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

# Reverse a charge in full

> Credits the full original amount back to the cardholder's wallet and marks the charge reversed. There is no request body and no amount parameter: partial refunds are not supported.

Only a `CHARGE` belonging to the calling merchant can be reversed, and only once. There is no time limit — a charge from any date can be reversed. Reversal also works while the wallet is suspended, so a freeze never traps a customer's money.

The VAT receipt is voided at the tax authority asynchronously, and if the charge was already paid out in an earlier settlement cycle the amount is netted against the merchant's next payout. Neither step blocks or delays this call.



## OpenAPI

````yaml /openapi.json post /v1/transactions/{txID}/reverse
openapi: 3.1.0
info:
  title: Vibepay Charge API
  version: 1.0.0
  description: >-
    The public API a merchant point-of-sale charges a Vibepay meal-benefit
    wallet against.


    A cardholder's card lives in Apple or Google Wallet as a pass that displays
    a rotating QR token. Your terminal scans that token, posts it here with an
    amount in tugrik, and the wallet is debited atomically. Every card charge
    also produces a Mongolian VAT receipt (ДДТД), issued asynchronously.


    This surface is authenticated per terminal with HTTP Basic and is intended
    for server-to-server or native POS callers. It sends no CORS headers, so a
    browser page cannot call it cross-origin.
  contact:
    name: Vibepay integrations
    email: info@vibepay.mn
    url: https://vibepay.mn
servers:
  - url: https://api.vibepay.mn
    description: >-
      Production. There is no separate sandbox — see the Environments guide for
      the small-amount charge-and-reverse loop used to verify an integration.
security:
  - terminalBasic: []
tags:
  - name: Transactions
    description: Charge a wallet, reverse a charge, and read this terminal's own history.
  - name: Service
    description: Unauthenticated liveness and readiness probes.
paths:
  /v1/transactions/{txID}/reverse:
    post:
      tags:
        - Transactions
      summary: Reverse a charge in full
      description: >-
        Credits the full original amount back to the cardholder's wallet and
        marks the charge reversed. There is no request body and no amount
        parameter: partial refunds are not supported.


        Only a `CHARGE` belonging to the calling merchant can be reversed, and
        only once. There is no time limit — a charge from any date can be
        reversed. Reversal also works while the wallet is suspended, so a freeze
        never traps a customer's money.


        The VAT receipt is voided at the tax authority asynchronously, and if
        the charge was already paid out in an earlier settlement cycle the
        amount is netted against the merchant's next payout. Neither step blocks
        or delays this call.
      operationId: reverseCharge
      parameters:
        - name: txID
          in: path
          required: true
          description: >-
            The `id` returned by the original charge, including its `tx_`
            prefix.
          schema:
            type: string
            pattern: ^tx_[0-9a-hjkmnp-tv-z]{26}$
          example: tx_01k2y7v9j0e8ra7cx3mbq4d5nf
      responses:
        '204':
          description: >-
            Reversed. The response body is empty — refetch the transaction if
            you need its updated state.
        '400':
          description: The path parameter is not a valid transaction id.
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/PlainTextError'
              examples:
                badId:
                  summary: Malformed id
                  value: |
                    invalid transaction id
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            The transaction belongs to another merchant, or it is not a charge.
            Both cases return the same body so a terminal cannot probe for
            transactions it does not own.
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/PlainTextError'
              examples:
                forbidden:
                  summary: Not yours, or not a charge
                  value: |
                    forbidden
        '404':
          description: No transaction with that id exists.
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/PlainTextError'
              examples:
                notFound:
                  summary: Unknown transaction
                  value: |
                    transaction not found
        '409':
          description: >-
            The charge was already reversed. Treat this as success and converge
            your local record to refunded — the customer has their money back.
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/PlainTextError'
              examples:
                alreadyReversed:
                  summary: Second reversal attempt
                  value: |
                    transaction already reversed
        '422':
          description: >-
            The wallet balance is too low to absorb the reversal. Unreachable on
            this route in practice, since reversing a charge credits the wallet
            rather than debiting it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeclineError'
              examples:
                underflow:
                  summary: Balance underflow
                  value:
                    error: wallet balance too low to reverse this charge
                    code: INSUFFICIENT_FUNDS
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  schemas:
    PlainTextError:
      type: string
      title: Plain-text error
      description: >-
        Every status other than `422` returns `text/plain; charset=utf-8` — a
        bare English message with a trailing newline, and no JSON and no error
        code. Branch on the HTTP status, not on this text.
    DeclineError:
      type: object
      title: Decline
      description: >-
        The body of a `422`. This is the only error shape on this API that is
        JSON; every other status returns plain text.
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: >-
            A human-readable English message, meant for logs and support
            conversations. Do not show it to a cashier and do not parse it — it
            can change.
          example: insufficient funds
        code:
          type: string
          description: >-
            The stable machine-readable reason. Branch on this and map it to
            your own localized cashier-facing message.
          enum:
            - INSUFFICIENT_FUNDS
            - DAILY_LIMIT_EXCEEDED
            - WEEKDAY_NOT_ALLOWED
            - TIME_NOT_ALLOWED
            - WALLET_SUSPENDED
            - CARD_CANCELLED
            - CARD_FROZEN
            - CARD_WALLET_MISMATCH
            - TOKEN_EXPIRED
            - TOKEN_INVALID
          example: INSUFFICIENT_FUNDS
  responses:
    Unauthorized:
      description: >-
        The credentials are missing, unrecognized, wrong, or belong to a
        suspended terminal. All four cases are indistinguishable by design. Stop
        retrying and re-pair the terminal.
      headers:
        WWW-Authenticate:
          description: Always `Basic realm="vibepay-terminal"`.
          schema:
            type: string
      content:
        text/plain:
          schema:
            $ref: '#/components/schemas/PlainTextError'
          examples:
            unauthorized:
              summary: Any authentication failure
              value: |
                unauthorized
    TooManyRequests:
      description: >-
        The API gateway's request ceiling was hit. The limit applies to the
        whole surface rather than to your terminal alone, so back off
        exponentially with jitter and retry — reusing the same `Idempotency-Key`
        if you are retrying a charge.
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
                description: Always `Too Many Requests`.
    InternalError:
      description: >-
        Something failed on Vibepay's side. The charge may or may not have been
        applied, so never assume it failed: retry with the same
        `Idempotency-Key`, or confirm with `GET /v1/transactions` before handing
        goods over.
      content:
        text/plain:
          schema:
            $ref: '#/components/schemas/PlainTextError'
          examples:
            internal:
              summary: Unexpected server error
              value: |
                internal error
  securitySchemes:
    terminalBasic:
      type: http
      scheme: basic
      description: >-
        Per-terminal HTTP Basic credentials, issued in the Vibepay merchant
        dashboard under Terminals. The username starts with `term_` and the
        password with `vpt_`. Every authentication failure — missing header,
        unknown username, wrong password, suspended terminal — returns the same
        `401`, so the response can never be used to discover valid usernames.

````