Activate recurring payment

Activates a pending SDP-custody recurring payment by creating the Solana subscriptions plan, authorizing the subscription, and storing the resulting on-chain identifiers and signatures.

POST/v1/payments/recurring-payments/{id}/activateRun in dashboard

A fresh activation signs the terms it read: when an edit lands while it runs, or expectedUpdatedAt names an earlier version, it returns 409 RECURRING_PAYMENT_CHANGED before claiming the schedule or signing.

Both the counterparty and its account must be active before a fresh activation; an unavailable recipient returns 404 before claiming the schedule or signing. Recovery can confirm saved transactions, but cannot sign a new step for an unavailable recipient.

The source wallet gives the Subscriptions program's authority for that token an approval of 0, and each collection approves exactly its own amount in the same transaction as the pull, so the token program's Approve instruction appears in activation and collection transactions. If your custody provider's wallet policy allowlists programs or instructions, allow Approve to that authority, or the provider refuses the collection and SDP pauses the recurring payment with pausedReason custody_policy_refused.

Requires an API key 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.

Request body

application/json

Optional precondition for activating a recurring payment.

  • expectedUpdatedAt · string · min length 1 · max length 64

    Optional updatedAt of the version you reviewed, from the last read of this recurring payment. When the recurring payment has changed since, the request returns 409 RECURRING_PAYMENT_CHANGED with the current updatedAt in error.details and changes nothing: read it again and review the current terms.

Responses

  • 200 Recurring payment activated

    • 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, destination_unavailable · 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, or destination_unavailable when its counterparty or account is archived or unavailable in the project. Resume requires an active recipient and clears the reason.

        • 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.

  • 502 Bad Gateway

    Same fields as 400.

  • 503 Service Unavailable

    Same fields as 400.

Example request

IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST 'https://api-beta.solana.com/v1/payments/recurring-payments/prp_example/activate' \
  -H "Authorization: Bearer $SDP_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
// Reuse this key when you retry this request.
const idempotencyKey = crypto.randomUUID();
const res = await fetch("https://api-beta.solana.com/v1/payments/recurring-payments/prp_example/activate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SDP_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({})
});
import os, requests, uuid

# Reuse this key when you retry this request.
idempotency_key = str(uuid.uuid4())
r = requests.post("https://api-beta.solana.com/v1/payments/recurring-payments/prp_example/activate",
  headers={"Authorization": f"Bearer {os.environ['SDP_API_KEY']}", "Idempotency-Key": idempotency_key},
  json={})

Example responses

JSON
{
  "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
{
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "Missing action: recurring.execute",
    "details": {
      "action": "recurring.execute"
    }
  },
  "meta": {
    "requestId": "req_example"
  }
}

Error responses use the standard error shape.