View as Markdown

llms.txt

No such blueprint

This instance publishes no blueprint at that address. The catalog lists every one it does publish.

Back to the blueprints

Blueprints Full integrations

Bill a customer on a schedule

Charge a new subscriber, keep their card, and put them on a monthly contract the platform bills for you, instead of running the schedule from your own service.

Signed in, you can run this blueprint against your own sandbox merchant one call at a time, with the values from each call threaded into the next. Sign in to run it.

8 steps, 6 API callsPaymentsCustomersContractsRecurring Billing

Samples use {{API_KEY}} for your API key and {{BASE_URL}} for this instance's API address. Anything else in double braces is a value an earlier step gave you.

Charge the first period

1. Charge the first period at signup

API call

POST /api/transactions

Take the subscriber's first payment as an ordinary card sale, with the card number on the request. A schedule bills from its start date forward, so the period the customer is buying right now is yours to charge. Keep the merchant id off the response: the customer you create below belongs to it.

Reference for this operation

Values this step gives you

  • {{merchantId}} The merchant the sale belongs to, from the response body's merchantId property.
cURL

curl -X POST "{{BASE_URL}}/api/transactions" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "cardData": {
      "cardNumber": "4111111111111111",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
.NET

using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");

var response = await http.PostAsJsonAsync("/api/transactions", new
{
    transactionType = "Sale",
    cardData = new
    {
        cardNumber = "4111111111111111",
        nameOnCard = "Jane Doe",
        expirationMonth = 12,
        expirationYear = 2030,
        cvv = 123
    },
    invoiceData = new
    {
        amounts = new { @base = 10.00m, total = 10.00m }
    }
});

response.EnsureSuccessStatusCode();

var sale = await response.Content.ReadFromJsonAsync<JsonElement>();
var merchantId = sale.GetProperty("merchantId").GetString();

What this step answers with

Abridged to the properties this step depends on. A real response carries more.

HTTP 200

{
  "id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
  "transactionType": "Sale",
  "resultCode": "Ok",
  "authorizedAmount": 10.00,
  "responseData": {
    "resultCode": "Ok",
    "resultMessage": "Approved"
  }
}

Set the customer up

2. Choose a name for the subscriber

On your side

Pick the name the customer record is created under. It has to be unique within the merchant: this platform refuses a second customer with a name another one already holds, and answers "Customer name must be unique within the same merchant." That's why the page asks rather than showing one, and why a sandbox merchant other people work through has names on it already. Use a fresh one each time you come back here, and in your own integration use whatever your system calls the subscriber.

Reference for this operation

Values this step gives you

  • {{customerName}} The name the customer is created under, and the name on their contact detail. You choose it, so nothing reads it off a response.

3. Create the customer the schedule belongs to

API call

POST /api/customers

A contract bills a customer, so the customer record comes first. It's created under the name you chose above, which also becomes the primary contact name. Give the address list exactly one default entry with a street line, a city, a state and a postal code. The list may be left empty, but a partial address is refused. Keep the id, and keep the creation timestamp: the schedule below starts the day the subscriber signed up.

Reference for this operation

Values this step gives you

  • {{customerId}} The id of the created customer, from the response body's id property.
  • {{scheduleStartDate}} The moment the customer record was created, from the response body's creationTime property. The contract below starts its schedule there.
cURL

curl -X POST "{{BASE_URL}}/api/customers" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "{{merchantId}}",
    "name": "{{customerName}}",
    "isActive": true,
    "contactDetail": {
      "primaryContactName": "{{customerName}}",
      "emailAddress": "jane.doe@example.com",
      "phone": "5555550123",
      "addresses": [
        {
          "address1": "100 Main Street",
          "city": "Minneapolis",
          "state": "MN",
          "zip": "55401",
          "isDefault": true
        }
      ]
    }
  }'
.NET

var customerName = "{{customerName}}";

var created = await http.PostAsJsonAsync("/api/customers", new
{
    merchantId,
    name = customerName,
    isActive = true,
    contactDetail = new
    {
        primaryContactName = customerName,
        emailAddress = "jane.doe@example.com",
        phone = "5555550123",
        addresses = new[]
        {
            new
            {
                address1 = "100 Main Street",
                city = "Minneapolis",
                state = "MN",
                zip = "55401",
                isDefault = true
            }
        }
    }
});

created.EnsureSuccessStatusCode();

var customer = await created.Content.ReadFromJsonAsync<JsonElement>();
var customerId = customer.GetProperty("id").GetString();
var scheduleStartDate = customer.GetProperty("creationTime").GetString();

What this step answers with

Abridged to the properties this step depends on. A real response carries more.

HTTP 200

{
  "id": "7b2c4d6e-8f0a-4c3d-9e5f-6a7b8c9d0e1f",
  "merchantId": "{{merchantId}}",
  "name": "{{customerName}}",
  "isActive": true,
  "creationTime": "2026-02-04T18:22:41.517Z"
}

4. Store the customer's card for the renewals

API call

POST /api/customers/add-stored-payment-method-async?customerId={{customerId}}

Send the card once more, this time to store it. The platform vaults it and answers with the opaque publicReference handle plus masked details you can show the customer, such as "Visa ending 1111" on a billing page. Store the publicReference against your subscriber record and nothing else about the card. Do this only when the customer has agreed you may keep their card for recurring charges, and keep a record of that agreement.

Reference for this operation

Values this step gives you

  • {{savedCardToken}} The opaque handle for the stored payment method, from the response body's publicReference property. The contract below is charged against this value.
cURL

curl -X POST \
  "{{BASE_URL}}/api/customers/add-stored-payment-method-async?customerId={{customerId}}" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "paymentMethodType": "Card",
    "customName": "Subscription card",
    "isDefault": true,
    "cardData": {
      "cardNumber": "4111111111111111",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    }
  }'
.NET

var stored = await http.PostAsJsonAsync(
    $"/api/customers/add-stored-payment-method-async?customerId={customerId}",
    new
    {
        paymentMethodType = "Card",
        customName = "Subscription card",
        isDefault = true,
        cardData = new
        {
            cardNumber = "4111111111111111",
            nameOnCard = "Jane Doe",
            expirationMonth = 12,
            expirationYear = 2030,
            cvv = 123
        }
    });

stored.EnsureSuccessStatusCode();

var method = await stored.Content.ReadFromJsonAsync<JsonElement>();

// The opaque pt_ handle. Store this against your subscriber and nothing else
// about the card.
var savedCardToken = method.GetProperty("publicReference").GetString();

What this step answers with

Abridged to the properties this step depends on. A real response carries more.

HTTP 200

{
  "id": "4e6f8a0b-2c3d-4e5f-9a6b-7c8d9e0f1a2b",
  "publicReference": "pt_7Qh2Kd4RmT9xLbVn",
  "paymentMethodType": "Card",
  "displayName": "Visa ending 1111",
  "origin": "Api",
  "isDefault": true
}

Put them on a schedule

5. Create the contract that carries the schedule

API call

POST /api/contracts

Name the customer, the stored payment method, the amount and the cadence, and the platform owns the timer from here. This is the step that replaces the nightly job in your own service. Send the handle on its own under payMethod. Raw card data sent beside a handle is rejected, and raw card data instead of one puts the number back in your system for every renewal. The amount has to be greater than zero, and the start date can't be in the past.

Reference for this operation

Values this step gives you

  • {{contractId}} The id of the created contract, from the response body's id property.
cURL

curl -X POST "{{BASE_URL}}/api/contracts" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Monthly subscription",
    "customerId": "{{customerId}}",
    "isActive": true,
    "payMethod": {
      "type": "Card",
      "paymentTokenReference": "{{savedCardToken}}"
    },
    "perBillInvoice": {
      "billSubtotalAmount": 10.00,
      "taxAmount": 0.00,
      "totalAmount": 10.00
    },
    "schedule": {
      "frequency": "Monthly",
      "interval": 1,
      "startDate": "{{scheduleStartDate}}"
    }
  }'
.NET

var contract = await http.PostAsJsonAsync("/api/contracts", new
{
    name = "Monthly subscription",
    customerId,
    isActive = true,
    payMethod = new
    {
        type = "Card",
        paymentTokenReference = savedCardToken
    },
    perBillInvoice = new
    {
        billSubtotalAmount = 10.00m,
        taxAmount = 0.00m,
        totalAmount = 10.00m
    },
    schedule = new
    {
        frequency = "Monthly",
        interval = 1,
        startDate = scheduleStartDate
    }
});

contract.EnsureSuccessStatusCode();

var created = await contract.Content.ReadFromJsonAsync<JsonElement>();
var contractId = created.GetProperty("id").GetString();

What this step answers with

Abridged to the properties this step depends on. A real response carries more.

HTTP 200

{
  "id": "2e4f6a8b-0c1d-4e5f-8a9b-0c1d2e3f4a5b",
  "customerId": "{{customerId}}",
  "name": "Monthly subscription",
  "isActive": true,
  "paymentMethodType": "Card",
  "amountPerAttempt": 10.00,
  "totalExecutions": 0,
  "payMethod": {
    "type": "Card",
    "paymentTokenReference": "{{savedCardToken}}"
  },
  "schedule": {
    "frequency": "Monthly",
    "interval": 1,
    "startDate": "{{scheduleStartDate}}",
    "nextScheduledRun": "{{scheduleStartDate}}"
  }
}

Watch the schedule run

6. Ask an administrator to run the schedule now

API call

POST /api/customers/recurring-billing/trigger

The schedule runs on the platform's own timer, so a live contract needs nothing further from you. To watch a run happen rather than wait for one, an administrator can start one on demand and narrow it to a single contract. This call is gated on an administrative permission, so your integration key can't make it and this platform doesn't run this step for you. Bring it to whoever administers the account.

Reference for this operation

Values this step gives you

    cURL

    curl -X POST "{{BASE_URL}}/api/customers/recurring-billing/trigger" \
      -H "api-key: {{API_KEY}}" \
      -H "Content-Type: application/json" \
      -d '{
        "contractIdFilter": "{{contractId}}",
        "reason": "Verifying a new subscription in the sandbox"
      }'
    .NET

    var run = await http.PostAsJsonAsync(
        "/api/customers/recurring-billing/trigger",
        new
        {
            contractIdFilter = contractId,
            reason = "Verifying a new subscription in the sandbox"
        });
    
    run.EnsureSuccessStatusCode();
    
    var triggered = await run.Content.ReadFromJsonAsync<JsonElement>();
    var runId = triggered.GetProperty("runId").GetString();

    What this step answers with

    Abridged to the properties this step depends on. A real response carries more.

    HTTP 200

    {
      "runId": "6a8b0c2d-4e5f-4a7b-8c9d-0e1f2a3b4c5d",
      "message": "Recurring billing run started."
    }

    7. Read what the run did to this contract

    API call

    GET /api/customers/recurring-billing/contracts/{{contractId}}/history

    Each run comes back with the contracts it touched, what it charged and what it couldn't. Read it to reconcile a billing day rather than inferring the outcome from your own records. This call is gated on the same administrative permission as the trigger above, so it's documented here rather than run for you. For the signal your own integration should act on, subscribe to the transaction webhooks instead: a contract charge raises the same events an ordinary sale does.

    Reference for this operation

    Values this step gives you

      cURL

      curl "{{BASE_URL}}/api/customers/recurring-billing/contracts/{{contractId}}/history" \
        -H "api-key: {{API_KEY}}"
      .NET

      var history = await http.GetFromJsonAsync<JsonElement>(
          $"/api/customers/recurring-billing/contracts/{contractId}/history");
      
      foreach (var summary in history.GetProperty("items").EnumerateArray())
      {
          var status = summary.GetProperty("status").GetString();
          var succeeded = summary.GetProperty("successCount").GetInt32();
          var failed = summary.GetProperty("failedCount").GetInt32();
      }

      What this step answers with

      Abridged to the properties this step depends on. A real response carries more.

      HTTP 200

      {
        "items": [
          {
            "runId": "6a8b0c2d-4e5f-4a7b-8c9d-0e1f2a3b4c5d",
            "merchantId": "{{merchantId}}",
            "status": "Completed",
            "triggerType": "Manual",
            "runDate": "2026-02-04T18:22:43.062Z",
            "contractCount": 1,
            "successCount": 1,
            "failedCount": 0,
            "skippedCount": 0,
            "totalAmount": 10.00
          }
        ],
        "pageItemCount": 1,
        "nextContinuationToken": null
      }

      8. When the subscription ends

      On your side

      Cancelling a subscription is two things, and doing only the first is the common mistake. Deactivate the contract so the schedule stops, and retire the stored payment method so the card you were given permission to keep stops being kept. "Save a card and charge it later" covers the retirement end to end, including why deactivating beats deleting once any transaction references the handle.

      Reference for this operation

      Values this step gives you

        Run the whole flow as one script

        Every step above in one script you can copy and run. Replace {{API_KEY}} with your own API key and {{BASE_URL}} with this instance's API address, and set any value the script asks you for at the top. A step that happens outside the API stays a comment.

        Code sample language

        brew install jq

        cURL

        #!/usr/bin/env bash
        # Bill a customer on a schedule
        #
        # Charge a new subscriber, keep their card, and put them on a monthly contract the platform bills
        # for you, instead of running the schedule from your own service.
        #
        # Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
        # it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        set -euo pipefail
        
        BASE_URL="{{BASE_URL}}"
        API_KEY="{{API_KEY}}"
        
        # Values you supply. Set each one before you run the script.
        # The name the customer is created under, and the name on their contact detail. You choose it, so
        # nothing reads it off a response.
        customerName=""
        
        # Sends one request and prints the response body. A failed call prints the API's answer and stops
        # the script.
        call() {
          local method="$1" path="$2" body="${3:-}" out
          local args=(-sS --fail-with-body -X "$method" "$BASE_URL$path" -H "api-key: $API_KEY")
          if [ -n "$body" ]; then
            args+=(-H "Content-Type: application/json" -d "$body")
          fi
          if ! out=$(curl "${args[@]}"); then
            printf '%s\n' "$out" >&2
            return 1
          fi
          printf '%s' "$out"
        }
        
        # Percent-encodes a value for use in a URL.
        urlencode() {
          jq -rn --arg value "$1" '$value | @uri'
        }
        
        # Phase 1: Charge the first period
        
        # Step 1: Charge the first period at signup
        step1=$(call POST "/api/transactions" '{
          "transactionType": "Sale",
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          },
          "invoiceData": {
            "amounts": { "base": 10.00, "total": 10.00 }
          }
        }')
        merchantId=$(jq -er '.merchantId | if . == null then error("The response carried no value for merchantId.") else tostring end' <<< "$step1")
        
        # Phase 2: Set the customer up
        
        # Step 2: Choose a name for the subscriber
        # Pick the name the customer record is created under. It has to be unique within the merchant: this
        # platform refuses a second customer with a name another one already holds, and answers "Customer
        # name must be unique within the same merchant." That's why the page asks rather than showing one,
        # and why a sandbox merchant other people work through has names on it already. Use a fresh one each
        # time you come back here, and in your own integration use whatever your system calls the
        # subscriber.
        
        # Step 3: Create the customer the schedule belongs to
        body=$(jq -n --arg merchantId "$merchantId" --arg customerName "$customerName" '{
          "merchantId": $merchantId,
          "name": $customerName,
          "isActive": true,
          "contactDetail": {
            "primaryContactName": $customerName,
            "emailAddress": "jane.doe@example.com",
            "phone": "5555550123",
            "addresses": [
              {
                "address1": "100 Main Street",
                "city": "Minneapolis",
                "state": "MN",
                "zip": "55401",
                "isDefault": true
              }
            ]
          }
        }')
        step3=$(call POST "/api/customers" "$body")
        customerId=$(jq -er '.id | if . == null then error("The response carried no value for customerId.") else tostring end' <<< "$step3")
        scheduleStartDate=$(jq -er '.creationTime | if . == null then error("The response carried no value for scheduleStartDate.") else tostring end' <<< "$step3")
        
        # Step 4: Store the customer's card for the renewals
        step4=$(call POST "/api/customers/add-stored-payment-method-async?customerId=$(urlencode "$customerId")" '{
          "paymentMethodType": "Card",
          "customName": "Subscription card",
          "isDefault": true,
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          }
        }')
        savedCardToken=$(jq -er '.publicReference | if . == null then error("The response carried no value for savedCardToken.") else tostring end' <<< "$step4")
        
        # Phase 3: Put them on a schedule
        
        # Step 5: Create the contract that carries the schedule
        body=$(jq -n --arg customerId "$customerId" --arg savedCardToken "$savedCardToken" --arg scheduleStartDate "$scheduleStartDate" '{
          "name": "Monthly subscription",
          "customerId": $customerId,
          "isActive": true,
          "payMethod": {
            "type": "Card",
            "paymentTokenReference": $savedCardToken
          },
          "perBillInvoice": {
            "billSubtotalAmount": 10.00,
            "taxAmount": 0.00,
            "totalAmount": 10.00
          },
          "schedule": {
            "frequency": "Monthly",
            "interval": 1,
            "startDate": $scheduleStartDate
          }
        }')
        step5=$(call POST "/api/contracts" "$body")
        contractId=$(jq -er '.id | if . == null then error("The response carried no value for contractId.") else tostring end' <<< "$step5")
        
        # Phase 4: Watch the schedule run
        
        # Step 6: Ask an administrator to run the schedule now
        # The schedule runs on the platform's own timer, so a live contract needs nothing further from you.
        # To watch a run happen rather than wait for one, an administrator can start one on demand and
        # narrow it to a single contract. This call is gated on an administrative permission, so your
        # integration key can't make it and this platform doesn't run this step for you. Bring it to whoever
        # administers the account.
        
        # Step 7: Read what the run did to this contract
        # Each run comes back with the contracts it touched, what it charged and what it couldn't. Read it
        # to reconcile a billing day rather than inferring the outcome from your own records. This call is
        # gated on the same administrative permission as the trigger above, so it's documented here rather
        # than run for you. For the signal your own integration should act on, subscribe to the transaction
        # webhooks instead: a contract charge raises the same events an ordinary sale does.
        
        # Step 8: When the subscription ends
        # Cancelling a subscription is two things, and doing only the first is the common mistake.
        # Deactivate the contract so the schedule stops, and retire the stored payment method so the card
        # you were given permission to keep stops being kept. "Save a card and charge it later" covers the
        # retirement end to end, including why deactivating beats deleting once any transaction references
        # the handle.
        

        PowerShell

        # Bill a customer on a schedule
        #
        # Charge a new subscriber, keep their card, and put them on a monthly contract the platform bills
        # for you, instead of running the schedule from your own service.
        #
        # Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
        # it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        $ErrorActionPreference = 'Stop'
        
        $baseUrl = '{{BASE_URL}}'
        $apiKey = '{{API_KEY}}'
        
        # Values you supply. Set each one before you run the script.
        # The name the customer is created under, and the name on their contact detail. You choose it, so
        # nothing reads it off a response.
        $customerName = ''
        
        # Sends one request and returns the parsed response body. A failed call stops the script.
        function Invoke-BlueprintCall([string] $Method, [string] $Path, [string] $Body) {
            $arguments = @{
                Method  = $Method
                Uri     = $baseUrl + $Path
                Headers = @{ 'api-key' = $apiKey }
            }
            if ($Body) {
                $arguments.ContentType = 'application/json'
                $arguments.Body = [System.Text.Encoding]::UTF8.GetBytes($Body)
            }
            Invoke-RestMethod @arguments
        }
        
        # Turns a value read off a response back into the text the API sent. A missing value stops the
        # script.
        function ConvertTo-CaptureValue($Value, [string] $Name) {
            if ($null -eq $Value) { throw "The response carried no value for $Name." }
            if ($Value -is [datetime] -and $Value.Kind -eq 'Unspecified') { return $Value.ToString('yyyy-MM-ddTHH:mm:ss.FFFFFFF', [cultureinfo]::InvariantCulture) }
            if ($Value -is [datetime]) { return $Value.ToUniversalTime().ToString('o') }
            if ($Value -is [bool]) { return $Value.ToString().ToLowerInvariant() }
            [string]$Value
        }
        
        # Phase 1: Charge the first period
        
        # Step 1: Charge the first period at signup
        $body = @'
        {
          "transactionType": "Sale",
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          },
          "invoiceData": {
            "amounts": { "base": 10.00, "total": 10.00 }
          }
        }
        '@
        $step1 = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body
        $merchantId = ConvertTo-CaptureValue $step1.merchantId 'merchantId'
        
        # Phase 2: Set the customer up
        
        # Step 2: Choose a name for the subscriber
        # Pick the name the customer record is created under. It has to be unique within the merchant: this
        # platform refuses a second customer with a name another one already holds, and answers "Customer
        # name must be unique within the same merchant." That's why the page asks rather than showing one,
        # and why a sandbox merchant other people work through has names on it already. Use a fresh one each
        # time you come back here, and in your own integration use whatever your system calls the
        # subscriber.
        
        # Step 3: Create the customer the schedule belongs to
        $body = @"
        {
          "merchantId": $(ConvertTo-Json -InputObject ([string]($merchantId))),
          "name": $(ConvertTo-Json -InputObject ([string]($customerName))),
          "isActive": true,
          "contactDetail": {
            "primaryContactName": $(ConvertTo-Json -InputObject ([string]($customerName))),
            "emailAddress": "jane.doe@example.com",
            "phone": "5555550123",
            "addresses": [
              {
                "address1": "100 Main Street",
                "city": "Minneapolis",
                "state": "MN",
                "zip": "55401",
                "isDefault": true
              }
            ]
          }
        }
        "@
        $step3 = Invoke-BlueprintCall -Method 'POST' -Path '/api/customers' -Body $body
        $customerId = ConvertTo-CaptureValue $step3.id 'customerId'
        $scheduleStartDate = ConvertTo-CaptureValue $step3.creationTime 'scheduleStartDate'
        
        # Step 4: Store the customer's card for the renewals
        $body = @'
        {
          "paymentMethodType": "Card",
          "customName": "Subscription card",
          "isDefault": true,
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          }
        }
        '@
        $step4 = Invoke-BlueprintCall -Method 'POST' -Path "/api/customers/add-stored-payment-method-async?customerId=$([uri]::EscapeDataString($customerId))" -Body $body
        $savedCardToken = ConvertTo-CaptureValue $step4.publicReference 'savedCardToken'
        
        # Phase 3: Put them on a schedule
        
        # Step 5: Create the contract that carries the schedule
        $body = @"
        {
          "name": "Monthly subscription",
          "customerId": $(ConvertTo-Json -InputObject ([string]($customerId))),
          "isActive": true,
          "payMethod": {
            "type": "Card",
            "paymentTokenReference": $(ConvertTo-Json -InputObject ([string]($savedCardToken)))
          },
          "perBillInvoice": {
            "billSubtotalAmount": 10.00,
            "taxAmount": 0.00,
            "totalAmount": 10.00
          },
          "schedule": {
            "frequency": "Monthly",
            "interval": 1,
            "startDate": $(ConvertTo-Json -InputObject ([string]($scheduleStartDate)))
          }
        }
        "@
        $step5 = Invoke-BlueprintCall -Method 'POST' -Path '/api/contracts' -Body $body
        $contractId = ConvertTo-CaptureValue $step5.id 'contractId'
        
        # Phase 4: Watch the schedule run
        
        # Step 6: Ask an administrator to run the schedule now
        # The schedule runs on the platform's own timer, so a live contract needs nothing further from you.
        # To watch a run happen rather than wait for one, an administrator can start one on demand and
        # narrow it to a single contract. This call is gated on an administrative permission, so your
        # integration key can't make it and this platform doesn't run this step for you. Bring it to whoever
        # administers the account.
        
        # Step 7: Read what the run did to this contract
        # Each run comes back with the contracts it touched, what it charged and what it couldn't. Read it
        # to reconcile a billing day rather than inferring the outcome from your own records. This call is
        # gated on the same administrative permission as the trigger above, so it's documented here rather
        # than run for you. For the signal your own integration should act on, subscribe to the transaction
        # webhooks instead: a contract charge raises the same events an ordinary sale does.
        
        # Step 8: When the subscription ends
        # Cancelling a subscription is two things, and doing only the first is the common mistake.
        # Deactivate the contract so the schedule stops, and retire the stored payment method so the card
        # you were given permission to keep stops being kept. "Save a card and charge it later" covers the
        # retirement end to end, including why deactivating beats deleting once any transaction references
        # the handle.
        

        TypeScript

        // Bill a customer on a schedule
        //
        // Charge a new subscriber, keep their card, and put them on a monthly contract the platform bills
        // for you, instead of running the schedule from your own service.
        //
        // Every API call in this blueprint, in order. Each value a call returns is passed to the calls
        // after it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        export {};
        
        const baseUrl = '{{BASE_URL}}';
        const apiKey = '{{API_KEY}}';
        
        // Values you supply. Set each one before you run the script.
        // The name the customer is created under, and the name on their contact detail. You choose it, so
        // nothing reads it off a response.
        const customerName = '';
        
        // Sends one request and returns the parsed response body. A failed call throws.
        async function call(method: string, path: string, body?: unknown): Promise<any> {
          const headers: Record<string, string> = { 'api-key': apiKey };
          if (body !== undefined) {
            headers['Content-Type'] = 'application/json';
          }
        
          const response = await fetch(baseUrl + path, {
            method,
            headers,
            body: body === undefined ? undefined : JSON.stringify(body),
          });
        
          const text = await response.text();
          if (!response.ok) {
            throw new Error(`${method} ${path} answered ${response.status}: ${text}`);
          }
        
          return text ? JSON.parse(text) : null;
        }
        
        // Turns a value read off a response into the text the API sent. A missing value throws.
        function capture(value: unknown, name: string): string {
          if (value === undefined || value === null) {
            throw new Error(`The response carried no value for ${name}.`);
          }
        
          return typeof value === 'string' ? value : JSON.stringify(value);
        }
        
        // Phase 1: Charge the first period
        
        // Step 1: Charge the first period at signup
        const step1 = await call('POST', '/api/transactions', {
          "transactionType": "Sale",
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          },
          "invoiceData": {
            "amounts": { "base": 10.00, "total": 10.00 }
          }
        });
        const merchantId = capture(step1.merchantId, 'merchantId');
        
        // Phase 2: Set the customer up
        
        // Step 2: Choose a name for the subscriber
        // Pick the name the customer record is created under. It has to be unique within the merchant: this
        // platform refuses a second customer with a name another one already holds, and answers "Customer
        // name must be unique within the same merchant." That's why the page asks rather than showing one,
        // and why a sandbox merchant other people work through has names on it already. Use a fresh one
        // each time you come back here, and in your own integration use whatever your system calls the
        // subscriber.
        
        // Step 3: Create the customer the schedule belongs to
        const step3 = await call('POST', '/api/customers', {
          "merchantId": merchantId,
          "name": customerName,
          "isActive": true,
          "contactDetail": {
            "primaryContactName": customerName,
            "emailAddress": "jane.doe@example.com",
            "phone": "5555550123",
            "addresses": [
              {
                "address1": "100 Main Street",
                "city": "Minneapolis",
                "state": "MN",
                "zip": "55401",
                "isDefault": true
              }
            ]
          }
        });
        const customerId = capture(step3.id, 'customerId');
        const scheduleStartDate = capture(step3.creationTime, 'scheduleStartDate');
        
        // Step 4: Store the customer's card for the renewals
        const step4 = await call('POST', `/api/customers/add-stored-payment-method-async?customerId=${encodeURIComponent(customerId)}`, {
          "paymentMethodType": "Card",
          "customName": "Subscription card",
          "isDefault": true,
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          }
        });
        const savedCardToken = capture(step4.publicReference, 'savedCardToken');
        
        // Phase 3: Put them on a schedule
        
        // Step 5: Create the contract that carries the schedule
        const step5 = await call('POST', '/api/contracts', {
          "name": "Monthly subscription",
          "customerId": customerId,
          "isActive": true,
          "payMethod": {
            "type": "Card",
            "paymentTokenReference": savedCardToken
          },
          "perBillInvoice": {
            "billSubtotalAmount": 10.00,
            "taxAmount": 0.00,
            "totalAmount": 10.00
          },
          "schedule": {
            "frequency": "Monthly",
            "interval": 1,
            "startDate": scheduleStartDate
          }
        });
        const contractId = capture(step5.id, 'contractId');
        
        // Phase 4: Watch the schedule run
        
        // Step 6: Ask an administrator to run the schedule now
        // The schedule runs on the platform's own timer, so a live contract needs nothing further from you.
        // To watch a run happen rather than wait for one, an administrator can start one on demand and
        // narrow it to a single contract. This call is gated on an administrative permission, so your
        // integration key can't make it and this platform doesn't run this step for you. Bring it to
        // whoever administers the account.
        
        // Step 7: Read what the run did to this contract
        // Each run comes back with the contracts it touched, what it charged and what it couldn't. Read it
        // to reconcile a billing day rather than inferring the outcome from your own records. This call is
        // gated on the same administrative permission as the trigger above, so it's documented here rather
        // than run for you. For the signal your own integration should act on, subscribe to the transaction
        // webhooks instead: a contract charge raises the same events an ordinary sale does.
        
        // Step 8: When the subscription ends
        // Cancelling a subscription is two things, and doing only the first is the common mistake.
        // Deactivate the contract so the schedule stops, and retire the stored payment method so the card
        // you were given permission to keep stops being kept. "Save a card and charge it later" covers the
        // retirement end to end, including why deactivating beats deleting once any transaction references
        // the handle.
        

        C#

        // Bill a customer on a schedule
        //
        // Charge a new subscriber, keep their card, and put them on a monthly contract the platform bills
        // for you, instead of running the schedule from your own service.
        //
        // Every API call in this blueprint, in order. Each value a call returns is passed to the calls
        // after it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        using System.Text;
        using System.Text.Json;
        
        var baseUrl = "{{BASE_URL}}";
        var apiKey = "{{API_KEY}}";
        
        // Values you supply. Set each one before you run the script.
        // The name the customer is created under, and the name on their contact detail. You choose it, so
        // nothing reads it off a response.
        var customerName = "";
        
        using var http = new HttpClient();
        http.DefaultRequestHeaders.Add("api-key", apiKey);
        
        // Sends one request and returns the parsed response body. A failed call throws.
        async Task<JsonElement> CallAsync(string method, string path, string? body = null)
        {
            using var request = new HttpRequestMessage(new HttpMethod(method), baseUrl + path);
            if (body is not null)
            {
                request.Content = new StringContent(body, Encoding.UTF8, "application/json");
            }
        
            using var response = await http.SendAsync(request);
            var json = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException($"{method} {path} answered {(int)response.StatusCode}: {json}");
            }
        
            return json.Length == 0 ? default : JsonSerializer.Deserialize<JsonElement>(json);
        }
        
        // Turns a value read off a response into the text the API sent. A missing value throws.
        static string Capture(JsonElement value, string name) => value.ValueKind switch
        {
            JsonValueKind.String => value.GetString()!,
            JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False => value.GetRawText(),
            _ => throw new InvalidOperationException($"The response carried no value for {name}.")
        };
        
        // Phase 1: Charge the first period
        
        // Step 1: Charge the first period at signup
        var step1 = await CallAsync("POST", "/api/transactions", """
            {
              "transactionType": "Sale",
              "cardData": {
                "cardNumber": "4111111111111111",
                "nameOnCard": "Jane Doe",
                "expirationMonth": 12,
                "expirationYear": 2030,
                "cvv": 123
              },
              "invoiceData": {
                "amounts": { "base": 10.00, "total": 10.00 }
              }
            }
            """);
        var merchantId = Capture(step1.GetProperty("merchantId"), "merchantId");
        
        // Phase 2: Set the customer up
        
        // Step 2: Choose a name for the subscriber
        // Pick the name the customer record is created under. It has to be unique within the merchant: this
        // platform refuses a second customer with a name another one already holds, and answers "Customer
        // name must be unique within the same merchant." That's why the page asks rather than showing one,
        // and why a sandbox merchant other people work through has names on it already. Use a fresh one
        // each time you come back here, and in your own integration use whatever your system calls the
        // subscriber.
        
        // Step 3: Create the customer the schedule belongs to
        var step3 = await CallAsync("POST", "/api/customers", $$"""
            {
              "merchantId": {{JsonSerializer.Serialize(merchantId)}},
              "name": {{JsonSerializer.Serialize(customerName)}},
              "isActive": true,
              "contactDetail": {
                "primaryContactName": {{JsonSerializer.Serialize(customerName)}},
                "emailAddress": "jane.doe@example.com",
                "phone": "5555550123",
                "addresses": [
                  {
                    "address1": "100 Main Street",
                    "city": "Minneapolis",
                    "state": "MN",
                    "zip": "55401",
                    "isDefault": true
                  }
                ]
              }
            }
            """);
        var customerId = Capture(step3.GetProperty("id"), "customerId");
        var scheduleStartDate = Capture(step3.GetProperty("creationTime"), "scheduleStartDate");
        
        // Step 4: Store the customer's card for the renewals
        var step4 = await CallAsync("POST", $"/api/customers/add-stored-payment-method-async?customerId={Uri.EscapeDataString(customerId)}", """
            {
              "paymentMethodType": "Card",
              "customName": "Subscription card",
              "isDefault": true,
              "cardData": {
                "cardNumber": "4111111111111111",
                "nameOnCard": "Jane Doe",
                "expirationMonth": 12,
                "expirationYear": 2030,
                "cvv": 123
              }
            }
            """);
        var savedCardToken = Capture(step4.GetProperty("publicReference"), "savedCardToken");
        
        // Phase 3: Put them on a schedule
        
        // Step 5: Create the contract that carries the schedule
        var step5 = await CallAsync("POST", "/api/contracts", $$"""
            {
              "name": "Monthly subscription",
              "customerId": {{JsonSerializer.Serialize(customerId)}},
              "isActive": true,
              "payMethod": {
                "type": "Card",
                "paymentTokenReference": {{JsonSerializer.Serialize(savedCardToken)}}
              },
              "perBillInvoice": {
                "billSubtotalAmount": 10.00,
                "taxAmount": 0.00,
                "totalAmount": 10.00
              },
              "schedule": {
                "frequency": "Monthly",
                "interval": 1,
                "startDate": {{JsonSerializer.Serialize(scheduleStartDate)}}
              }
            }
            """);
        var contractId = Capture(step5.GetProperty("id"), "contractId");
        
        // Phase 4: Watch the schedule run
        
        // Step 6: Ask an administrator to run the schedule now
        // The schedule runs on the platform's own timer, so a live contract needs nothing further from you.
        // To watch a run happen rather than wait for one, an administrator can start one on demand and
        // narrow it to a single contract. This call is gated on an administrative permission, so your
        // integration key can't make it and this platform doesn't run this step for you. Bring it to
        // whoever administers the account.
        
        // Step 7: Read what the run did to this contract
        // Each run comes back with the contracts it touched, what it charged and what it couldn't. Read it
        // to reconcile a billing day rather than inferring the outcome from your own records. This call is
        // gated on the same administrative permission as the trigger above, so it's documented here rather
        // than run for you. For the signal your own integration should act on, subscribe to the transaction
        // webhooks instead: a contract charge raises the same events an ordinary sale does.
        
        // Step 8: When the subscription ends
        // Cancelling a subscription is two things, and doing only the first is the common mistake.
        // Deactivate the contract so the schedule stops, and retire the stored payment method so the card
        // you were given permission to keep stops being kept. "Save a card and charge it later" covers the
        // retirement end to end, including why deactivating beats deleting once any transaction references
        // the handle.
        

        pip install requests

        Python

        # Bill a customer on a schedule
        #
        # Charge a new subscriber, keep their card, and put them on a monthly contract the platform bills
        # for you, instead of running the schedule from your own service.
        #
        # Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
        # it. A step that happens outside the API is a comment, and any failed call stops the script.
        
        from urllib.parse import quote
        
        import json
        import requests
        
        BASE_URL = "{{BASE_URL}}"
        API_KEY = "{{API_KEY}}"
        
        # Values you supply. Set each one before you run the script.
        # The name the customer is created under, and the name on their contact detail. You choose it, so
        # nothing reads it off a response.
        customer_name = ""
        
        
        # Sends one request and returns the parsed response body. A failed call raises.
        def call(method, path, body=None):
            response = requests.request(
                method,
                BASE_URL + path,
                headers={"api-key": API_KEY},
                json=body,
            )
            response.raise_for_status()
            return response.json() if response.content else None
        
        
        # Turns a value read off a response into the text the API sent. A missing value raises.
        def capture(value, name):
            if value is None:
                raise ValueError(f"The response carried no value for {name}.")
            return value if isinstance(value, str) else json.dumps(value)
        
        
        # Phase 1: Charge the first period
        
        # Step 1: Charge the first period at signup
        step1 = call("POST", "/api/transactions", {
          "transactionType": "Sale",
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          },
          "invoiceData": {
            "amounts": { "base": 10.00, "total": 10.00 }
          }
        })
        merchant_id = capture(step1["merchantId"], "merchantId")
        
        # Phase 2: Set the customer up
        
        # Step 2: Choose a name for the subscriber
        # Pick the name the customer record is created under. It has to be unique within the merchant: this
        # platform refuses a second customer with a name another one already holds, and answers "Customer
        # name must be unique within the same merchant." That's why the page asks rather than showing one,
        # and why a sandbox merchant other people work through has names on it already. Use a fresh one each
        # time you come back here, and in your own integration use whatever your system calls the
        # subscriber.
        
        # Step 3: Create the customer the schedule belongs to
        step3 = call("POST", "/api/customers", {
          "merchantId": merchant_id,
          "name": customer_name,
          "isActive": True,
          "contactDetail": {
            "primaryContactName": customer_name,
            "emailAddress": "jane.doe@example.com",
            "phone": "5555550123",
            "addresses": [
              {
                "address1": "100 Main Street",
                "city": "Minneapolis",
                "state": "MN",
                "zip": "55401",
                "isDefault": True
              }
            ]
          }
        })
        customer_id = capture(step3["id"], "customerId")
        schedule_start_date = capture(step3["creationTime"], "scheduleStartDate")
        
        # Step 4: Store the customer's card for the renewals
        step4 = call("POST", f"/api/customers/add-stored-payment-method-async?customerId={quote(customer_id, safe='')}", {
          "paymentMethodType": "Card",
          "customName": "Subscription card",
          "isDefault": True,
          "cardData": {
            "cardNumber": "4111111111111111",
            "nameOnCard": "Jane Doe",
            "expirationMonth": 12,
            "expirationYear": 2030,
            "cvv": 123
          }
        })
        saved_card_token = capture(step4["publicReference"], "savedCardToken")
        
        # Phase 3: Put them on a schedule
        
        # Step 5: Create the contract that carries the schedule
        step5 = call("POST", "/api/contracts", {
          "name": "Monthly subscription",
          "customerId": customer_id,
          "isActive": True,
          "payMethod": {
            "type": "Card",
            "paymentTokenReference": saved_card_token
          },
          "perBillInvoice": {
            "billSubtotalAmount": 10.00,
            "taxAmount": 0.00,
            "totalAmount": 10.00
          },
          "schedule": {
            "frequency": "Monthly",
            "interval": 1,
            "startDate": schedule_start_date
          }
        })
        contract_id = capture(step5["id"], "contractId")
        
        # Phase 4: Watch the schedule run
        
        # Step 6: Ask an administrator to run the schedule now
        # The schedule runs on the platform's own timer, so a live contract needs nothing further from you.
        # To watch a run happen rather than wait for one, an administrator can start one on demand and
        # narrow it to a single contract. This call is gated on an administrative permission, so your
        # integration key can't make it and this platform doesn't run this step for you. Bring it to whoever
        # administers the account.
        
        # Step 7: Read what the run did to this contract
        # Each run comes back with the contracts it touched, what it charged and what it couldn't. Read it
        # to reconcile a billing day rather than inferring the outcome from your own records. This call is
        # gated on the same administrative permission as the trigger above, so it's documented here rather
        # than run for you. For the signal your own integration should act on, subscribe to the transaction
        # webhooks instead: a contract charge raises the same events an ordinary sale does.
        
        # Step 8: When the subscription ends
        # Cancelling a subscription is two things, and doing only the first is the common mistake.
        # Deactivate the contract so the schedule stops, and retire the stored payment method so the card
        # you were given permission to keep stops being kept. "Save a card and charge it later" covers the
        # retirement end to end, including why deactivating beats deleting once any transaction references
        # the handle.
        

        Reconnecting to the server

        Could not reconnect

        This session has ended

        Attempt 1

        Your work on this page is still here. Retrying keeps it; reloading starts the page again.

        The server no longer holds this page's state, so it has to be loaded again.