cimplify
API reference

Checkout

Create a durable purchase intent, review immutable terms and an exact payment option, then confirm it idempotently. An Order does not exist until confirmation commits.

Checkout is a two-step purchase-intent protocol. Minting freezes the current Cart, buyer, funding allocations, and eligible collection options into immutable terms. Confirmation accepts one exact terms version and payment option. It is the only step that may create an Order.

All responses use Cache-Control: no-store. Keep the same idempotency key when a request has an ambiguous network outcome. Rotate it only when the shopper changes material checkout or tender input.

POST /api/v1/purchase-intents

Mint an open Cart purchase intent. The authenticated publishable key determines the seller; callers cannot supply seller or owner authority.

Request

cURL
curl -X POST https://storefronts.cimplify.io/api/v1/purchase-intents \
  -H "X-API-Key: cpk_test_your_publishable_key" \
  -H "Idempotency-Key: 5b1f9d80-2c70-4f2c-bf61-a0e6fa6b02a1" \
  -H "Content-Type: application/json" \
  -d '{
    "client_intent_key": "5b1f9d80-2c70-4f2c-bf61-a0e6fa6b02a1",
    "source": { "kind": "cart", "cart_id": "cart_01H…" },
    "checkout": {
      "customer": {
        "name": "Ama Mensah",
        "email": "ama@example.com",
        "phone": "+233241234567",
        "save_details": false
      },
      "order_type": "delivery",
      "address_info": {
        "street_address": "12 Independence Ave",
        "city": "Accra",
        "country": "GH"
      },
      "tender_preference": {
        "method_kind": "mobile_money",
        "authority": {
          "authority_kind": "transient",
          "phone_number": "+233241234567",
          "network": "mtn"
        }
      }
    }
  }'

tender_preference controls option ranking, not commercial authority. A saved Mobile Money preference contains only authority_kind: "saved_reference" and its opaque stored_instrument_id; never resend its phone number. The server still returns all eligible options.

Response

{
  "data": {
    "id": "pi_01J…",
    "status": "open",
    "revision": 1,
    "current_terms": {
      "id": "pit_01J…",
      "intent_revision": 1,
      "terms_version": 1,
      "money": {
        "currency": "GHS",
        "total_obligation": "105.00",
        "due_now": "105.00",
        "rail_amount": "105.00",
        "store_credit_consumed": "0.00",
        "voucher_credit_consumed": "0.00"
      },
      "payment_options": [
        {
          "id": "pio_01J…",
          "rank": 0,
          "method_kind": "mobile_money",
          "collection_mode": "request_to_pay",
          "provider": "mtn_momo",
          "principal_amount": "105.00",
          "fee_amount": "1.50",
          "customer_debit_amount": "106.50",
          "currency": "GHS",
          "status": "eligible",
          "expected_next_action": "provider_approval",
          "expires_at": "2026-08-23T18:30:00Z"
        }
      ],
      "valid_until": "2026-08-23T18:30:00Z"
    }
  }
}

Funding and collection have distinct consent semantics:

store_credit_consumed + voucher_credit_consumed + rail_amount = due_now

For the selected option:

principal_amount + fee_amount = customer_debit_amount

Show the selected option's fee and customer debit before confirmation. There is no truthful top-level payment fee because each option may price collection differently.

GET /api/v1/purchase-intents/:intent_id

Read the current owner-fenced intent and immutable terms. Use this after a reload or an ambiguous mint response instead of creating another intent.

POST /api/v1/purchase-intents/:intent_id/prepare

Prepare or update Cart material for an existing draft/open intent. The request is:

{
  "expected_intent_revision": 0,
  "idempotency_key": "prepare_01J…",
  "checkout": {
    "customer": {
      "name": "Ama Mensah",
      "phone": "+233241234567",
      "email": "ama@example.com",
      "save_details": false
    },
    "order_type": "delivery",
    "address_info": {
      "street_address": "12 Independence Ave",
      "city": "Accra",
      "country": "GH"
    },
    "tender_preference": { "method_kind": "card" }
  }
}

An exact (intent, idempotency_key) replay returns the original prepared terms. Contact, address, voucher, and raw tender data must not be used as browser recovery fingerprints.

POST /api/v1/purchase-intents/:intent_id/confirm

Confirm the exact reviewed terms and option. accepted_customer_debit_amount comes from the selected option, including that option's fee; it is not the provider-neutral rail_amount.

cURL
curl -X POST https://storefronts.cimplify.io/api/v1/purchase-intents/pi_01J…/confirm \
  -H "X-API-Key: cpk_test_your_publishable_key" \
  -H "Idempotency-Key: confirm_01J…" \
  -H "Content-Type: application/json" \
  -d '{
    "accepted_intent_revision": 1,
    "accepted_terms_version": 1,
    "accepted_due_now": "105.00",
    "accepted_customer_debit_amount": "106.50",
    "currency": "GHS",
    "payment_option_id": "pio_01J…",
    "idempotency_key": "confirm_01J…"
  }'

The response is a closed union. Terminal/recoverable outcomes include order_created, collection_processing, payment_failed, and payment_outcome_unknown. Provider interaction may return redirect_required, card_popup_required, or authorization_required. Commercial revalidation may return terms_changed, selection_unavailable, offer_ended, or source_unavailable; render those states and require a fresh review rather than silently accepting replacements.

For a true zero-rail purchase, payment_option_id is null and accepted_customer_debit_amount is zero. Any issued option must be selected by its opaque ID.

POST /api/v1/purchase-intents/:intent_id/attempts/:attempt_id/actions

Submit one typed provider challenge for the exact attempt. The body contains only the authorization answer:

{
  "authorization": { "type": "otp", "value": "847362" }
}

Never send an Order ID, Payment ID, provider reference, phone number, or raw payment credential to this endpoint.

Hosted checkout sessions

Hosted sessions own their purchase intent through the session authority. Use POST /v1/checkout/sessions/:session_id/prepare, /confirm, GET /replay, and /attempts/:attempt_id/actions. Do not call direct storefront intent routes with a hosted intent ID. In the SDK, use client.purchaseIntents.forHostedSession(sessionId).

  • Next: Orders Read the Order only after confirmation returns order_created.

  • FX Lock a quote before preparing terms when the shopper pays in another currency.

On this page