---
title: "Create a payment link"
method: POST
path: "/v1/payment-links"
tags: ["Payment links"]
---

# Create a payment link

`POST /v1/payment-links`

## Request

- Base URL: `https://api.getfinero.com/functions/v1/api`
- URL: `https://api.getfinero.com/functions/v1/api/v1/payment-links`
- Auth: HTTP bearer

Creates a hosted payment link for one collectible invoice or installment through the tenant's connected payment provider — the same validation and creation path the Finero app uses. An invoice target or installment may carry SEVERAL links at once, each collecting part of it: an installment of 100.00 can be offered as four links of 25.00. Their amounts may not exceed what is still outstanding. When live links already cover the installment the request is refused as a conflict, exactly as a duplicate link always was; when there is room but less than was asked for, it is refused as an invalid amount and names what was available. If the tenant's email-notification workflow is enabled, the customer payment-link email is sent as a consequence.

Requires an **admin** API key (has side effects).

## Headers

- `Idempotency-Key` string, required

## Request body

- object
  - `invoice_id` string, uuid, required: Invoice to collect.
  - `installment_id` string, uuid, nullable, required: Real installment id in installment mode; null in invoice-only mode. Read the invoice collection_mode first.
  - `integration_id` string, uuid: Optional: a specific connected payments integration. Defaults to the tenant's connected provider.
  - `amount_minor` integer: Optional: how much this link collects, in integer minor units (100 = USD 1.00; 100 = JPY 100). Fractional minor units are rejected, never rounded. Omit to collect everything still available on the current collection target, which is the usual case. Supply it to split an installment across several links — the amounts of all live links may not exceed the installment's `collection_outstanding`. That figure already has what Finero collected netted off it, so do not subtract `amount_collected` as well. For a three-decimal currency (BHD, JOD, KWD, OMR, TND) the amount must be a multiple of 10: the payment provider drops the last digit, so 1005 fils would be refused as `validation_failed` rather than minted as a link the checkout cannot honour.
  - `payment_due_date` string, date: Optional link-owned due date (YYYY-MM-DD). Omit it to snapshot the invoice or installment target's current due date when the link is committed. Past dates are accepted for already-overdue receivables.
  - `email_delivery_mode` 'workflow' | 'send_now' | 'schedule' | 'suppress': Optional link-specific email decision. Omit or use workflow to preserve tenant workflow behaviour. Explicit modes override it for this link.
  - `email_scheduled_local` string: Tenant-local YYYY-MM-DDTHH:mm wall clock. Required only when email_delivery_mode is schedule; the backend resolves it through the tenant timezone.

## Response `201`

Success

- object
  - `id` string, uuid, required: Payment link id.
  - `invoice_id` string, uuid, required: Invoice the link collects.
  - `installment_id` string, uuid, nullable, required: Installment the link collects. An installment may carry SEVERAL live links (0280) — their amounts together may not exceed the installment's `collection_outstanding`, which the database enforces under a row lock. What Finero already collected is ALREADY netted off that figure; subtracting it a second time under-states the room.
  - `status` 'active' | 'processing' | 'paid' | 'inactive' | 'needs_replacement' | 'action_required', required: Lifecycle status. active = payable now; processing = a payment is being confirmed; paid = settled; inactive = deactivated or superseded; needs_replacement = the provider-side link stopped being usable (expired or removed at the provider), so a new link must be created; action_required = frozen pending a human decision — Finero will not act on it automatically. A payment whose provider report does not match the request is frozen in processing, not here.
  - `provider` 'stripe' | 'wix', required: Payment provider hosting the checkout.
  - `mode` 'test' | 'live', required: Provider environment. `test` means a TEST processor handled this money: it is not revenue and must never be reported as cash collected, however real the amount looks. `live` is real money.
  - `amount_minor` integer, required: Amount the link collects. Integer amount in the currency's MINOR unit (e.g. cents), as defined by the PAYMENT PROVIDER's own table, which is not always ISO 4217 — the two disagree on ISK and MGA among others, so converting with an ISO exponent misprices those. Invoice and installment amounts are exact decimal strings and are the safe basis for arithmetic and display.
  - `currency` string, required: Link currency. ISO 4217 alphabetic code, e.g. "USD".
  - `payment_due_date` string, date, nullable, required: Business due date owned by this payment link (YYYY-MM-DD). It is snapshotted at creation and does not change when the source installment changes. Null only for legacy links whose source had no date.
  - `url` string, nullable, required: Hosted customer-facing pay-page URL; null once the link is withdrawn (`inactive`). Treat as a capability: anyone with the URL can open the checkout.
  - `paid_at` string, date-time, nullable, required: When the link was paid (ISO 8601 UTC).
  - `paid_amount_minor` integer, nullable, required: Settled amount. Integer amount in the currency's MINOR unit (e.g. cents), as defined by the PAYMENT PROVIDER's own table, which is not always ISO 4217 — the two disagree on ISK and MGA among others, so converting with an ISO exponent misprices those. Invoice and installment amounts are exact decimal strings and are the safe basis for arithmetic and display.
  - `paid_currency` string, nullable, required: Settled currency. ISO 4217 alphabetic code, e.g. "USD".
  - `deactivated_at` string, date-time, nullable, required: When the link was deactivated (ISO 8601 UTC).
  - `created_at` string, date-time, required: Creation time (ISO 8601 UTC).
  - `updated_at` string, date-time, required: Last update time (ISO 8601 UTC).
  - `incident` object, nullable, required: A payment the provider reported on this link that Finero refused to record — null almost always. While it is open the link stays as it is (a frozen link reads `processing`), so the same money cannot be asked for twice. Read-only here: a workspace admin checks it at the provider and resolves it in the app's Payments view, which closes the link so a new one can be created.
    - `reason` 'amount_mismatch' | 'currency_mismatch' | 'mode_mismatch' | 'attempt_request_mismatch' | 'attempt_tenant_mismatch' | 'attempt_not_found' | 'duplicate_settlement', required: Why Finero refused to record the payment. duplicate_settlement = the link was already paid and a second payment arrived; the others = what the provider reported does not match what this link asked for.
    - `provider` 'stripe' | 'wix', required: Provider that reported the payment.
    - `reported_amount_minor` integer, required: Amount the provider reported. Integer amount in the currency's MINOR unit (e.g. cents), as defined by the PAYMENT PROVIDER's own table, which is not always ISO 4217 — the two disagree on ISK and MGA among others, so converting with an ISO exponent misprices those. Invoice and installment amounts are exact decimal strings and are the safe basis for arithmetic and display.
    - `reported_currency` string, required: Currency the provider reported. ISO 4217 alphabetic code, e.g. "USD".
    - `recorded_at` string, date-time, required: When Finero recorded the incident (ISO 8601 UTC).

## Other responses

- `400`: `idempotency_key_required` — This operation requires the Idempotency-Key header (1–255 characters). `validation_failed` — The request failed schema validation. See error.details for field messages.
- `401`: `invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.
- `403`: `feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).
- `404`: `not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.
- `409`: `conflict` — State conflict — e.g. live payment links already cover the installment, or a concurrent identical request is in flight. `idempotency_conflict` — The Idempotency-Key was already used with a DIFFERENT request body. Use a fresh key for a new request.
- `413`: `payload_too_large` — The request body exceeds the 64 KiB limit.
- `422`: `amount_exceeds_available` — The installment is collectible, but the amount asked for is more than is still available on it. The available amount is the installment's collection_outstanding minus the amounts of its live payment links; what Finero already collected is netted off collection_outstanding and must not be subtracted twice. Ask for that amount or less, or omit amount_minor to take exactly what is left. `currency_not_supported` — The selected payment provider cannot collect the invoice's currency (single-currency providers can only charge in their store currency). Use a provider that supports it, or collect the invoice outside Finero. `installment_not_collectible` — The invoice or installment target cannot be collected right now — it is already paid, excluded from collections, has no outstanding balance, its invoice is not collection-ready, or its invoice is no longer present in your ERP. GET /v1/invoices/{invoiceId} reports the invoice's collection_status and each installment's collectible flag. `integration_unavailable` — The tenant has no connected payments integration able to mint this link, or the one selected is not ready. Connect or repair a payment provider in the Finero app, then retry.
- `429`: `rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying — waiting that long is enough. The RateLimit-* fields on every response say how much headroom is left before this happens; pace against them instead of retrying into a refusal.
- `500`: `internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.

---

[API](https://skmtc.dev/getfinero/apis/finero-public-api.md) · [All operations](https://skmtc.dev/getfinero/apis/finero-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/getfinero/apis/finero-public-api/revisions/3d456e222775?raw)
