---
title: "Create payment request"
method: POST
path: "/v1/payments/requests"
tags: ["Payments"]
---

# Create payment request

`POST /v1/payments/requests`

## Request

- Base URL: `http://localhost:8787`
- URL: `http://localhost:8787/v1/payments/requests`
- Auth: HTTP bearer

Creates a payment request: an amount of one token for a payer to send to a custody wallet in the caller's project, through the request's hosted payment link. Requires `payment_requests.manage`. `walletId` is the provider wallet ID (`walletId` from `GET /v1/wallets`), not the SDP Wallet ID transfers take; an unknown wallet, or one outside the project, returns 404. `amount` is a decimal string with at most the mint's decimals, so `0.05` and `0.050` are the same request; more decimals return 400. A native SOL request is refused with 409 `AMOUNT_BELOW_RENT_MINIMUM` when the wallet has no account and the amount is below the rent-exempt minimum. The response is the only one besides `GET /v1/payments/requests/{paymentRequestId}/link` that carries `publicToken`. Accepts an Idempotency-Key: a retry with the same key and request returns the original payment request instead of creating a second one.

## Headers

- `x-project-id` string: Required for dashboard sessions. API keys use their assigned project.
- `Idempotency-Key` string: Optional, and honored when sent. 1-255 printable ASCII characters; send a UUID per logical operation and reuse it on every retry. Within 24 hours, a retry with the same key and the same request returns the stored status and body with the response header `Idempotent-Replayed: true`. Keys are scoped to the credential: within 24 hours, the same key with a different request, or from a different credential, returns 422 `IDEMPOTENCY_KEY_REUSED`; after that, the same key from a different credential runs as a new request. A rotated API key is a new credential, so after any rotation read the resource with GET to see whether the original request ran, and never resend a money request under the new credential without checking first. A key whose original request is still running returns 409 `IDEMPOTENCY_KEY_IN_FLIGHT` with `Retry-After`. A 400, 401, 403, 409 or 429 refusal is not stored, so the same key runs again once the cause is fixed. Access is checked before the key on every request, so a refused request never claims the key, and a credential that has since lost access to the named resource gets 403 instead of the stored response. A replay is a snapshot of the original response: read the current state with GET instead of sending the request again. If the original request never recorded its outcome (it crashed), a same-key retry gets 409 `IDEMPOTENCY_KEY_IN_FLIGHT` until 15 minutes plus the route's lease have passed since the original was sent (about 16 minutes, or about 20 for transfer batches), then 409 `IDEMPOTENCY_KEY_OUTCOME_UNKNOWN` with no `Retry-After`, as does every later retry with that key: read the resource with GET to see whether it ran, then send again with a new key. A 5xx is stored and replayed like any other answer; retry it with a new key.

## Request body

- object: Create payment request payload.
  - `walletId` string, required: Receiving custody wallet in the caller's project. Provider wallet ID: the `walletId` field returned by GET /v1/wallets. The wallet record `id` and the public key are not accepted. An SDP Wallet ID (`cwlt_...`) returns 404.
  - `token` string, required: Mint address of the token to request. Symbols are not accepted; for native SOL, pass `So11111111111111111111111111111111111111112`.
  - `amount` string, required: Amount to request in UI units, as a decimal string greater than zero with no more decimal places than the mint has.
  - `expiresAt` string, date-time, nullable: When the request stops accepting payment, as an ISO 8601 timestamp. Null or omitted: it never expires.

## Response `201`

Payment request created

- object: Standard success response envelope.
  - `data` object, required: A new payment request, with its link token.
    - `id` string, required: SDP payment request ID.
    - `organizationId` string, required: Organization ID.
    - `projectId` string, nullable, required: Project ID.
    - `walletId` string, required: Receiving custody wallet's provider ID.
    - `destinationAddress` string, required: Address the payment is sent to.
    - `token` string, required: Token mint address.
    - `amount` string, required: Token amount in UI units (decimal string).
    - `reference` string, required: Solana Pay reference account that identifies the payment on chain.
    - `status` 'awaiting_payment' | 'paid' | 'canceled' | 'expired', required: Payment request status. `awaiting_payment` is open; `paid` was settled by an on-chain payment; `canceled` and `expired` closed without one.
    - `expiresAt` string, date-time, nullable, required: Expiry, if any.
    - `fulfilledByTransferId` string, nullable, required: The inbound transfer that paid the request.
    - `paidLate` boolean, required: Whether the payment landed after the request expired or was canceled. A payer who signed before then can still pay.
    - `lastCheckedAt` string, date-time, nullable, required: When SDP last read the chain for the payment.
    - `canceledByApiKeyId` string, nullable, required: The API key that canceled the request; null when a member did, or it is not canceled.
    - `canceledByUserId` string, nullable, required: User ID of the member who canceled the request; null when an API key did, or it is not canceled.
    - `canceledAt` string, date-time, nullable, required: When it was canceled.
    - `lifecycle` object[], required: Every status the request entered, oldest first.
      - `status` 'awaiting_payment' | 'paid' | 'canceled' | 'expired', required: Payment request status. `awaiting_payment` is open; `paid` was settled by an on-chain payment; `canceled` and `expired` closed without one.
      - `at` string, date-time, required: ISO 8601 timestamp.
    - `createdBy` string, nullable, required: User ID of the member, or of the API key's creator, who created it.
    - `createdAt` string, date-time, required: ISO 8601 timestamp.
    - `updatedAt` string, date-time, required: ISO 8601 timestamp.
    - `publicToken` string, required: Token behind the request's hosted payment link, `/pay/{publicToken}`. Anyone holding it can build the request's fee-sponsored payment transaction, so share it only with the payer. Reads never return it; get it again with `GET /v1/payments/requests/{paymentRequestId}/link`.
  - `meta` object, required: Response metadata.
    - `requestId` string, required: Request identifier for tracing.
    - `timestamp` string, date-time, required: ISO 8601 timestamp.

## Other responses

- `400`: Error
- `401`: Error
- `403`: Forbidden. Requires the `payment_requests.manage` action: an API key must hold it (a module's `manage` or `execute` also covers its `read`), and a signed-in member's organization role must include it. Without it the error is INSUFFICIENT_PERMISSIONS and `error.details.action` names the action. Any other 403 names its own reason.
- `404`: Error
- `409`: Error
- `422`: Error
- `500`: Error

## Changes

- **2026-11-04** `b329212c2291`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/solana/apis/solana-developer-platform-api/changes/v1/payments/requests/post.md)

---

[API](https://skmtc.dev/solana/apis/solana-developer-platform-api.md) - [All operations](https://skmtc.dev/solana/apis/solana-developer-platform-api/llms.txt) - [OpenAPI document](https://skmtc.dev/solana/apis/solana-developer-platform-api/revisions/b329212c2291?raw)
