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

# Refund a payment

> Refunds a settled sale and returns the refund it created. Only a sale can be refunded —
passing a refund's own id answers 404, exactly as reading it through
`GET /v1/commerce/payments/{id}` does.

`amount_cents` is the amount to return in minor units and must be a positive integer.
Omit the field entirely to refund everything still refundable on the payment; an explicit
`null` is refused, so there is exactly one way to ask for a full refund. It is also the only
field the body accepts: any other key is refused with 422 rather than ignored, so a misspelled
field name cannot quietly become a full refund.

The refund is refused with 422 when the payment cannot be refunded at all: it is already
charged back, already fully refunded, past its refund window, or split across tenders.

`Idempotency-Key` is required. A retry carrying the same key and the same body returns the
original refund; the same key with a different body is refused with 409. Two deliberate
refunds of the same size need two different keys — that is the only thing distinguishing
them from a retry.



## OpenAPI

````yaml /api-reference/commerce-v1.openapi.json post /v1/commerce/payments/{id}/refunds
openapi: 3.0.3
info:
  description: >-
    Public REST API for COPE vendor integrations. Authenticate with a COPE API
    key or Clerk bearer token and call the endpoints described below.
  title: COPE Public API
  version: v1
servers:
  - description: Production
    url: https://api.cope.com
security:
  - cope_sk: []
paths:
  /v1/commerce/payments/{id}/refunds:
    post:
      tags:
        - Refunds
      summary: Refund a payment
      description: >-
        Refunds a settled sale and returns the refund it created. Only a sale
        can be refunded —

        passing a refund's own id answers 404, exactly as reading it through

        `GET /v1/commerce/payments/{id}` does.


        `amount_cents` is the amount to return in minor units and must be a
        positive integer.

        Omit the field entirely to refund everything still refundable on the
        payment; an explicit

        `null` is refused, so there is exactly one way to ask for a full refund.
        It is also the only

        field the body accepts: any other key is refused with 422 rather than
        ignored, so a misspelled

        field name cannot quietly become a full refund.


        The refund is refused with 422 when the payment cannot be refunded at
        all: it is already

        charged back, already fully refunded, past its refund window, or split
        across tenders.


        `Idempotency-Key` is required. A retry carrying the same key and the
        same body returns the

        original refund; the same key with a different body is refused with 409.
        Two deliberate

        refunds of the same size need two different keys — that is the only
        thing distinguishing

        them from a retry.
      operationId: createCommerceRefund
      parameters:
        - description: id public identifier.
          example: py_A1b2C3d4E5f6
          in: path
          name: id
          required: true
          schema:
            example: py_A1b2C3d4E5f6
            pattern: ^py_(?:[A-Za-z0-9]{12}|[0-9a-f]{24})$
            type: string
        - description: >-
            Required. At most 255 characters of valid UTF-8 with no NUL byte. A
            retry that carries the same key and the same body returns the
            original response instead of performing the write a second time. The
            same key with a different body is refused with 409
            idempotency_conflict. Keys are remembered for 24 hours.
          in: header
          name: Idempotency-Key
          required: true
          schema:
            maxLength: 255
            type: string
      requestBody:
        content:
          application/json:
            schema:
              additionalProperties: false
              properties:
                amount_cents:
                  description: >-
                    Amount to refund in minor units. Omit the field entirely to
                    refund everything still refundable; an explicit null is
                    refused.
                  minimum: 1
                  type: integer
              type: object
      responses:
        '201':
          content:
            application/json:
              schema:
                additionalProperties: false
                properties:
                  data:
                    additionalProperties: false
                    properties:
                      amount_cents:
                        type: integer
                      cash_flow_at:
                        nullable: true
                        type: string
                      created_at:
                        type: string
                      currency:
                        type: string
                      id:
                        example: py_A1b2C3d4E5f6
                        pattern: ^py_(?:[A-Za-z0-9]{12}|[0-9a-f]{24})$
                        type: string
                      order:
                        additionalProperties: false
                        nullable: true
                        properties:
                          id:
                            example: ord_A1b2C3d4E5f6
                            pattern: ^ord_[A-Za-z0-9]{12}$
                            type: string
                        required:
                          - id
                        type: object
                      payment_method:
                        nullable: true
                        type: string
                      payment_type:
                        type: string
                      refunded_payment:
                        additionalProperties: false
                        nullable: true
                        properties:
                          id:
                            example: py_A1b2C3d4E5f6
                            pattern: ^py_(?:[A-Za-z0-9]{12}|[0-9a-f]{24})$
                            type: string
                        required:
                          - id
                        type: object
                      status:
                        type: string
                    required:
                      - id
                      - status
                      - payment_type
                      - payment_method
                      - amount_cents
                      - currency
                      - cash_flow_at
                      - created_at
                      - order
                      - refunded_payment
                    type: object
                required:
                  - data
                type: object
          description: Successful response
        '400':
          content:
            application/problem+json:
              examples:
                idempotency_key_required:
                  summary: Missing Idempotency-Key header
                  value:
                    code: idempotency_key_required
                    detail: Idempotency-Key header is required for this operation
                    errors:
                      - code: idempotency_key_required
                        detail: Idempotency-Key header is required for this operation
                    request_id: req_123
                    status: 400
                    title: Idempotency Key Required
                    type: https://docs.cope.com/errors/idempotency_key_required
                unparsable_request:
                  summary: Unparsable request
                  value:
                    code: invalid_request
                    detail: >-
                      The request could not be parsed. Check the query string
                      and the request body.
                    request_id: req_123
                    status: 400
                    title: Invalid Request
                    type: https://docs.cope.com/errors/invalid_request
                invalid_idempotency_key:
                  summary: Malformed Idempotency-Key header
                  value:
                    code: invalid_idempotency_key
                    detail: >-
                      Idempotency-Key header must be at most 255 characters of
                      valid UTF-8 without a NUL byte
                    request_id: req_123
                    status: 400
                    title: Invalid Idempotency Key
                    type: https://docs.cope.com/errors/invalid_idempotency_key
              schema:
                $ref: '#/components/schemas/PublicProblemDetail'
          description: Invalid request
        '401':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/PublicProblemDetail'
          description: Missing or invalid bearer token
        '403':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/PublicProblemDetail'
          description: Bearer token is not authorized for this route
        '404':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/PublicProblemDetail'
          description: Resource not found
        '409':
          content:
            application/problem+json:
              examples:
                idempotency_conflict_in_flight:
                  summary: A request with this key is still processing
                  value:
                    code: idempotency_conflict
                    detail: >-
                      A request with this idempotency key is currently being
                      processed
                    request_id: req_123
                    status: 409
                    title: Idempotency Conflict
                    type: https://docs.cope.com/errors/idempotency_conflict
                idempotency_conflict_body_mismatch:
                  summary: The key was reused with a different body
                  value:
                    code: idempotency_conflict
                    detail: >-
                      Request body does not match the original request for this
                      idempotency key
                    request_id: req_123
                    status: 409
                    title: Idempotency Conflict
                    type: https://docs.cope.com/errors/idempotency_conflict
              schema:
                $ref: '#/components/schemas/PublicProblemDetail'
          description: >-
            The idempotency key cannot be honoured: either the first request
            carrying it is still processing, or it was reused with a different
            body. Retry the first case unchanged to collect the original
            response; the second needs the original body, or a new key for a
            genuinely new request.
        '422':
          content:
            application/problem+json:
              examples:
                validation_failed:
                  summary: Validation failed
                  value:
                    code: validation_failed
                    detail: null
                    errors:
                      - code: blank
                        detail: Name can't be blank
                        param: name
                    request_id: req_123
                    status: 422
                    title: Validation Failed
                    type: https://docs.cope.com/errors/validation_failed
              schema:
                $ref: '#/components/schemas/PublicProblemDetail'
          description: Public commerce validation problem
        '502':
          content:
            application/problem+json:
              examples:
                refund_rejected:
                  summary: The processor did not confirm the refund
                  value:
                    code: refund_rejected
                    detail: 'Refund failed (stripe): charge_already_refunded'
                    request_id: req_123
                    status: 502
                    title: Refund Rejected
                    type: https://docs.cope.com/errors/refund_rejected
                refund_recording_failed:
                  summary: The refund was executed but could not be recorded
                  value:
                    code: refund_recording_failed
                    detail: >-
                      The processor executed this refund but it could not be
                      recorded here. Retry this exact request — the identical
                      request cannot refund twice, and recording may succeed on
                      the retry. A retry carrying a different amount would issue
                      a second, real refund.
                    request_id: req_123
                    status: 502
                    title: Refund Recording Failed
                    type: https://docs.cope.com/errors/refund_recording_failed
              schema:
                $ref: '#/components/schemas/PublicProblemDetail'
          description: >-
            The processor did not confirm the refund. `detail` carries its own
            message. A rejection means nothing was refunded; a connection
            failure means the outcome is unknown and the refund may already
            exist at the processor, and `refund_recording_failed` means it
            certainly does. In every case the safe retry is the identical
            request, with the same Idempotency-Key and the same body — never a
            different amount, which issues a second, real refund.
        '503':
          content:
            application/problem+json:
              examples:
                refund_dependency_unavailable:
                  summary: A dependency could not answer, so no refund was attempted
                  value:
                    code: seller_balance_unavailable
                    detail: The refund was not attempted; retry shortly.
                    request_id: req_123
                    status: 503
                    title: Seller Balance Unavailable
                    type: https://docs.cope.com/errors/seller_balance_unavailable
              schema:
                $ref: '#/components/schemas/PublicProblemDetail'
          description: >-
            A dependency the refund needs could not answer, so no refund was
            attempted and no money moved. Retry the identical request with the
            same Idempotency-Key; a failed attempt does not spend the key.
      security:
        - cope_sk: []
components:
  schemas:
    PublicProblemDetail:
      additionalProperties: false
      properties:
        code:
          type: string
        detail:
          nullable: true
          type: string
        errors:
          items:
            additionalProperties: false
            properties:
              code:
                type: string
              detail:
                type: string
              param:
                type: string
            required:
              - code
              - detail
            type: object
          type: array
        request_id:
          type: string
        status:
          type: integer
        title:
          type: string
        type:
          type: string
      required:
        - type
        - title
        - status
        - code
        - request_id
      type: object
  securitySchemes:
    cope_sk:
      description: >-
        Bearer credential for the public API. Vendor integrations should send a
        live COPE API key (`cope_sk_live_*`). Clerk bearer tokens are also
        accepted when paired with `X-Cope-Business-Id`.
      scheme: bearer
      type: http

````