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

# Payment links

> Shareable payment URLs, single use or reusable, with no need to know the debtor in advance.

A **payment link** (`PaymentLink`) is a **public payment URL** that you can share without knowing the debtor in advance. When someone pays, TapiPay internally generates a [debt](/en/resources/debts) for that payment.

**When to use it:** charges without a debtor roster (donations, one-off sales, checkout links over WhatsApp or email), where you know the payer at the moment of payment, not before. If you already know the debtor, a [debt](/en/resources/debts) is usually better.

## Two usage modes

<CardGroup cols={2}>
  <Card title="Single use" icon="ticket">
    `singleUse: true` (default). Generates a debt with the first payment and is then deactivated. Ideal for a single invoice or one-off sale.
  </Card>

  <Card title="Reusable" icon="infinity">
    `singleUse: false`. Created without a debtor and accepts multiple payments while it stays active. Donations or payment gateway mode.
  </Card>
</CardGroup>

## Create a link

The minimum is the amount and a description visible on the payment page.

```bash theme={null}
curl --request POST \
  --url 'https://tapipay-facade.dev.tapila.cloud/payment-links' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'x-authorization-token: YOUR_TAPI_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "externalPaymentLinkId": "LINK-DONACION-01",
    "amount": 500.00,
    "currency": "MXN",
    "description": "2026 campaign donation",
    "expiresAt": "2026-12-31T23:59:59Z",
    "singleUse": false
  }'
```

### Main fields

<ResponseField name="amount" type="number" required>
  Amount in **pesos**, with up to 2 decimal places (minimum `0.01`).
</ResponseField>

<ResponseField name="description" type="string" required>
  Description visible on the payment page.
</ResponseField>

<ResponseField name="externalPaymentLinkId" type="string">
  Natural key of the link. Optional, but if you send it, it must be unique per organization (a duplicate returns `409 RESOURCE_CONFLICT`).
</ResponseField>

<ResponseField name="singleUse" type="boolean" default="true">
  `true` deactivates after the first payment; `false` accepts multiple payments.
</ResponseField>

<ResponseField name="identifierValue" type="string">
  Your own identifier for the URL (optional). It is sanitized to a URL-safe format (lowercase, alphanumerics and hyphens, maximum 64 characters). If it ends up empty after sanitizing, it returns `400 INVALID_REQUEST`.
</ResponseField>

<ResponseField name="expiresAt" type="string" required>
  Expiration date and time (ISO 8601). Must be in the future. It is required.
</ResponseField>

<ResponseField name="successUrl" type="string">
  Redirect URL when the payment is completed. If you do not send it, the Tapi page is used.
</ResponseField>

It also accepts `currency`, `productName`, and `metadata`.

## The `identifierValue` and the payment URL

The `paymentUrl` always comes in the response. How the `identifierValue` is built depends on whether the link has a known debtor:

<Tabs>
  <Tab title="Without a known debtor">
    This is the typical case for a link. The `identifierValue` **is exposed** in the response and is the final segment of the `paymentUrl`, so you can correlate it without parsing the URL.

    * If you sent an `identifierValue`, your sanitized value is used.
    * If not, TapiPay generates a **synthetic** one with the format `{3letters}-{random}` (the 3 letters come from your organization's name; for example, "Acme Corp" produces `acm-x3kM9pQr`).

    ```json theme={null}
    {
      "data": {
        "paymentLinkId": "plk_7nS9uV3d",
        "status": "ACTIVE",
        "amount": 4500.00,
        "currency": "MXN",
        "description": "One-time invoice #777",
        "singleUse": true,
        "identifierValue": "acm-x3kM9pQr",
        "paymentUrl": "https://app.tapipay.la/s/acme-corp/portal/acm-x3kM9pQr/",
        "createdAt": "2026-06-03T19:20:00Z"
      },
      "requestId": "req_a1b2c3d4"
    }
    ```
  </Tab>

  <Tab title="With a known debtor">
    If you create the link with `externalClientId` or `contactData`, the internal identifier is the debtor's and **is omitted** from the response to avoid exposing personal data (PII-safe). The `paymentUrl` may come resolved or `null` if it cannot be generated safely.
  </Tab>
</Tabs>

## Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> ACTIVE
    ACTIVE --> PAID: payment (if singleUse)
    ACTIVE --> EXPIRED: expiresAt elapses
    ACTIVE --> CANCELLED: cancellation
    PAID --> [*]
    EXPIRED --> [*]
    CANCELLED --> [*]
```

| Status      | Meaning                                                           |
| ----------- | ----------------------------------------------------------------- |
| `ACTIVE`    | Available to receive payments.                                    |
| `PAID`      | Paid. Applies to `singleUse: true` links after the first payment. |
| `EXPIRED`   | Passed `expiresAt`. No longer accepts payments.                   |
| `CANCELLED` | Cancelled manually. Final status.                                 |

<Note>
  A reusable link (`singleUse: false`) stays `ACTIVE` and accumulates payments. Each payment generates an internal debt; you query them with `GET /payment-links/{id}/payments`.
</Note>

## Update without invalidating the URL

`PATCH /payment-links/{id}` lets you change `description`, `expiresAt`, and `metadata` **without changing the `paymentUrl`**: the link stays accessible from the same URL. The `amount`, `currency`, and `singleUse` are **not** modifiable (trying to do so returns `400 INVALID_REQUEST`).

## Endpoints

| Method  | Path                           | Description                                    |
| ------- | ------------------------------ | ---------------------------------------------- |
| `POST`  | `/payment-links`               | Create a link.                                 |
| `GET`   | `/payment-links/{id}`          | Retrieve a link.                               |
| `GET`   | `/payment-links`               | List links.                                    |
| `PATCH` | `/payment-links/{id}`          | Update `description`, `expiresAt`, `metadata`. |
| `POST`  | `/payment-links/{id}/cancel`   | Cancel.                                        |
| `GET`   | `/payment-links/{id}/payments` | List the payments received.                    |

<Card title="Try it in the API Reference" icon="play" href="/api-reference">
  Explore each endpoint with its interactive playground.
</Card>
