> Solana Developer Platform docs, served as Markdown. The [docs index](https://platform.solana.com/docs.md) lists every page and [llms.txt](https://platform.solana.com/docs/ai/llms.txt) the key ones; [SKILL.md](https://platform.solana.com/SKILL.md) has the rules for building on SDP.

# Update recurring payment

Updates an SDP-custody recurring payment.

`PATCH /v1/payments/recurring-payments/{id}`

Pending records are updated directly. Active metadata and due-date edits are applied in place, while active term, source, destination, or token edits create a replacement Solana subscription, authorize it, cancel the old subscription, and then swap the recurring payment to the replacement records. Requires an Idempotency-Key, because an edit can sign and broadcast a replacement subscription. A same-key retry of a completed edit replays its answer. A retry of an interrupted edit gets 409 IDEMPOTENCY_KEY_IN_FLIGHT with Retry-After until the attempt is stale, then resumes from the last step SDP recorded. If a step was sent but not yet recorded when the edit was interrupted, which is rare, the retry can fail or send that step again, so read the recurring payment with GET before editing it again.

Requires an [API key](/docs/developing-with-sdp/authentication) with the `recurring.execute` action.

## Path parameters

- **`id`** · `string` · required · min length 1 · example `prp_example`

  SDP recurring payment record ID.

## Headers

- **`x-project-id`** · `string` · min length 1 · example `prj_example`

  Required for dashboard sessions. API keys use their assigned project.

- **`Idempotency-Key`** · `string` · required

  Use a unique 1–255 printable ASCII key per operation. Reuse it with the same request on retries. An in-flight attempt returns `409`; a changed request returns `422`. [Retry and recovery rules](/docs/developing-with-sdp/idempotency).

## Request body

`application/json` · required

Updates a recurring payment. Pending payments are edited directly. Active metadata or due-date updates are applied in place, while active term/source/destination/token updates create and authorize a replacement subscription before canceling the old one.

- **`sourceCustodyWalletId`** · `string` · min length 1 · example `cwlt_example`

  Optional exact replacement SDP Wallet ID (`id` from `GET /v1/wallets`). Provider `walletId`, wallet addresses, and the legacy `sourceWalletId` selector are not accepted. Active replacements require write access to both the old and new source wallets.

- **`counterpartyId`** · `string` · min length 1 · example `cpty_example`

  Optional replacement counterparty. When provided, counterpartyAccountId is also required.

- **`counterpartyAccountId`** · `string` · min length 1 · example `cpacct_example`

  Optional replacement counterparty account. Without counterpartyId, this changes the account for the current counterparty.

- **`token`** · `string` · example `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`

  Optional replacement token. Must be a USD stablecoin (e.g. USDC) or a token issued in this project; native SOL is not supported.

- **`amount`** · `string` · example `25.00`

  Optional replacement amount in UI units.

- **`periodHours`** · `integer` · greater than 0 · max 8760 · example `720`

  Optional replacement billing period length in hours. Active term changes create a replacement on-chain subscription.

- **`firstCollectionAt`** · `string | null` · format `date-time` · example `2099-01-01T00:00:00.000Z`

  Pending-only first collection timestamp. Use null to clear the pending first collection override.

- **`nextCollectionDueAt`** · `string | null` · format `date-time` · example `2099-02-01T00:00:00.000Z`

  Active-only next due timestamp. Use null to reset to the earliest eligible collection time.

- **`metadataUri`** · `string | null` · format `uri` · max length 128 · example `https://example.com/subscriptions/monthly-usdc.json`

  Optional plan metadata URI. Use null to clear it. Active metadata-only edits update the existing on-chain plan in place.

## Responses

- **`200`** Recurring payment updated

  - **`data`** · `object` · required

    Recurring payment response payload.

    - **`recurringPayment`** · `object` · required

      SDP-custody outbound recurring payment record.

      - **`id`** · `string` · required · example `prp_example`

        SDP recurring payment ID.

      - **`organizationId`** · `string` · required · min length 1 · example `org_example`

        Organization identifier.

      - **`projectId`** · `string` · required · min length 1 · example `prj_example`

        Project identifier.

      - **`sourceCustodyWalletId`** · `string | null` · required · example `cwlt_example`

        Exact source SDP Wallet ID, or null for unresolved legacy records.

      - **`sourceProviderWalletId`** · `string` · required · min length 1 · example `privy_wallet_123`

        Source Provider wallet ID retained as evidence.

      - **`sourceAddress`** · `string` · required · min length 32 · max length 44 · example `So11111111111111111111111111111111111111112`

        Source wallet address.

      - **`counterpartyId`** · `string` · required · example `cpty_example`

        Counterparty ID.

      - **`counterpartyAccountId`** · `string` · required · example `cpacct_example`

        Counterparty account ID.

      - **`destinationAddress`** · `string` · required · min length 32 · max length 44 · example `So11111111111111111111111111111111111111112`

        Counterparty wallet owner address.

      - **`destinationTokenAccount`** · `string | null` · required · min length 32 · max length 44 · example `So11111111111111111111111111111111111111112`

        Derived counterparty token account used for collection.

      - **`token`** · `string` · required

        SPL token mint address.

      - **`amount`** · `string` · required · example `100.00`

        Token amount in UI units (decimal string).

      - **`periodHours`** · `integer` · required · greater than 0 · example `720`

      - **`firstCollectionAt`** · `string | null` · required · format `date-time` · example `2025-01-01T00:00:00.000Z`

        Requested first collection timestamp.

      - **`nextCollectionDueAt`** · `string | null` · required · format `date-time` · example `2025-01-01T00:00:00.000Z`

        Next due collection timestamp.

      - **`planId`** · `string | null` · required

        Linked SDP subscription plan ID.

      - **`subscriptionId`** · `string | null` · required

        Linked SDP subscription ID.

      - **`planPda`** · `string | null` · required · min length 32 · max length 44 · example `So11111111111111111111111111111111111111112`

        On-chain plan PDA.

      - **`planCreatedAt`** · `string | null` · required

        On-chain plan createdAt value as an unsigned integer string.

      - **`planCreationSignature`** · `string | null` · required

        Solana signature for plan creation.

      - **`subscriptionPda`** · `string | null` · required · min length 32 · max length 44 · example `So11111111111111111111111111111111111111112`

        On-chain subscription PDA.

      - **`subscriptionAuthorityAddress`** · `string | null` · required · min length 32 · max length 44 · example `So11111111111111111111111111111111111111112`

        On-chain subscription authority address.

      - **`authorizationSignature`** · `string | null` · required

        Solana signature for subscription authorization.

      - **`status`** · `string` · required · one of `pending_activation`, `activating`, `active`, `updating`, `canceling`, `resuming`, `paused`, `canceled`, `expired` · example `active`

        Recurring payment status.

      - **`pausedReason`** · `string | null` · required · one of `custody_policy_refused`, `failed_attempts` · example `null`

        Why SDP paused the recurring payment, set exactly while `status` is `paused`: `custody_policy_refused` when the custody provider's policy refused to sign a collection, `failed_attempts` when one due period failed 5 times. Resume clears it.

      - **`metadataUri`** · `string | null` · required

        Optional plan metadata URI.

      - **`createdBy`** · `string | null` · required

        Creator user ID or API key ID.

      - **`createdAt`** · `string` · required · format `date-time` · example `2025-01-01T00:00:00.000Z`

        ISO 8601 timestamp.

      - **`updatedAt`** · `string` · required · format `date-time` · example `2025-01-01T00:00:00.000Z`

        ISO 8601 timestamp.

    - **`tokenApproval`** · `object`

      Present when the recurring payment is canceled, on the cancel response and on get: what became of the token approval it relied on.

      - **`status`** · `string` · required · one of `revoked`, `kept`, `revoking` · example `kept`

        `revoked` once the approval has been revoked since the cancel. `kept` while other live recurring payments on the same wallet and token, in this or another project, still use it; `sharedWith` lists the ones in this project. It is revoked once the last of them ends. `revoking` when nothing else uses it but the revoke has not confirmed yet, for example while a collection that was already processing settles; the background sweep finishes it.

      - **`delegate`** · `string` · required · min length 32 · max length 44 · example `So11111111111111111111111111111111111111112`

        The approval's delegate: the Subscriptions program's authority for this wallet and token.

      - **`sharedWith`** · `object[]` · required

        The other live recurring payments in this project on the same wallet and token that keep the approval. Empty unless `status` is `kept`, and empty then when only recurring payments in other projects keep it.

        - **`id`** · `string` · required · example `prp_example`

          SDP recurring payment ID.

        - **`status`** · `string` · required · one of `pending_activation`, `activating`, `active`, `updating`, `canceling`, `resuming`, `paused`, `canceled`, `expired` · example `active`

          Recurring payment status.

  - **`meta`** · `object` · required

    Response metadata.

    - **`requestId`** · `string` · required · min length 1 · example `req_example`

      Request identifier for tracing.

    - **`timestamp`** · `string` · required · format `date-time` · example `2025-01-01T00:00:00.000Z`

      ISO 8601 timestamp.

- **`400`** Bad Request

  - **`error`** · `object` · required

    Standard error payload.

    - **`code`** · `string` · required

      Machine-readable error code.

    - **`message`** · `string` · required

      Human-readable error message.

    - **`details`** · `object`

      Optional error details for debugging.

      - **`*`** · `any`

  - **`meta`** · `object`

    - **`requestId`** · `string` · min length 1 · example `req_example`

      Request identifier for tracing.

- **`401`** Unauthorized

  Same fields as `400`.

- **`403`** Forbidden. Requires the `recurring.execute` 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.

  Same fields as `400`.

- **`404`** Not Found

  Same fields as `400`.

- **`409`** Conflict

  Same fields as `400`.

- **`422`** Unprocessable Entity

  Same fields as `400`.

- **`500`** Internal Server Error

  Same fields as `400`.

## Example request

```bash tab="cURL"
curl -X PATCH 'https://api.solana.com/v1/payments/recurring-payments/prp_example' \
  -H "Authorization: Bearer $SDP_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceCustodyWalletId": "cwlt_example",
    "counterpartyId": "cpty_example",
    "counterpartyAccountId": "cpacct_example",
    "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "amount": "25.00",
    "periodHours": 720,
    "firstCollectionAt": "2099-01-01T00:00:00.000Z",
    "nextCollectionDueAt": "2099-02-01T00:00:00.000Z",
    "metadataUri": "https://example.com/subscriptions/monthly-usdc.json"
  }'
```

```ts tab="TypeScript"
const res = await fetch("https://api.solana.com/v1/payments/recurring-payments/prp_example", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.SDP_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "sourceCustodyWalletId": "cwlt_example",
    "counterpartyId": "cpty_example",
    "counterpartyAccountId": "cpacct_example",
    "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "amount": "25.00",
    "periodHours": 720,
    "firstCollectionAt": "2099-01-01T00:00:00.000Z",
    "nextCollectionDueAt": "2099-02-01T00:00:00.000Z",
    "metadataUri": "https://example.com/subscriptions/monthly-usdc.json"
  })
});
```

```python tab="Python"
import os, requests, uuid

r = requests.patch("https://api.solana.com/v1/payments/recurring-payments/prp_example",
  headers={"Authorization": f"Bearer {os.environ['SDP_API_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
  json={
    "sourceCustodyWalletId": "cwlt_example",
    "counterpartyId": "cpty_example",
    "counterpartyAccountId": "cpacct_example",
    "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "amount": "25.00",
    "periodHours": 720,
    "firstCollectionAt": "2099-01-01T00:00:00.000Z",
    "nextCollectionDueAt": "2099-02-01T00:00:00.000Z",
    "metadataUri": "https://example.com/subscriptions/monthly-usdc.json"
  })
```

## Example responses

```json tab="200"
{
  "data": {
    "recurringPayment": {
      "id": "prp_example",
      "organizationId": "org_example",
      "projectId": "prj_example",
      "sourceCustodyWalletId": "cwlt_example",
      "sourceProviderWalletId": "privy_wallet_123",
      "sourceAddress": "So11111111111111111111111111111111111111112",
      "counterpartyId": "cpty_example",
      "counterpartyAccountId": "cpacct_example",
      "destinationAddress": "So11111111111111111111111111111111111111112",
      "destinationTokenAccount": "So11111111111111111111111111111111111111112",
      "token": "example",
      "amount": "100.00",
      "periodHours": 720,
      "firstCollectionAt": "2025-01-01T00:00:00.000Z",
      "nextCollectionDueAt": "2025-01-01T00:00:00.000Z",
      "planId": "plan_example",
      "subscriptionId": "subscription_example",
      "planPda": "So11111111111111111111111111111111111111112",
      "planCreatedAt": "example",
      "planCreationSignature": "example",
      "subscriptionPda": "So11111111111111111111111111111111111111112",
      "subscriptionAuthorityAddress": "So11111111111111111111111111111111111111112",
      "authorizationSignature": "example",
      "status": "active",
      "pausedReason": null,
      "metadataUri": "example",
      "createdBy": "example",
      "createdAt": "2025-01-01T00:00:00.000Z",
      "updatedAt": "2025-01-01T00:00:00.000Z"
    },
    "tokenApproval": {
      "status": "kept",
      "delegate": "So11111111111111111111111111111111111111112",
      "sharedWith": [
        {
          "id": "prp_example",
          "status": "active"
        }
      ]
    }
  },
  "meta": {
    "requestId": "req_example",
    "timestamp": "2025-01-01T00:00:00.000Z"
  }
}
```

```json tab="403"
{
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "Missing action: recurring.execute",
    "details": {
      "action": "recurring.execute"
    }
  },
  "meta": {
    "requestId": "req_example"
  }
}
```

Error responses use the [standard error shape](/docs/reference/conventions/errors).
