cimplify
API reference

Link

/v1/link/*: customer-scoped Link API. Hosted on api.cimplify.io, separate from the per-business storefront API.

The Cimplify Link API is customer-scoped and cross-business: saved addresses, saved mobile money, and account preferences belong to a customer, not a business. It runs on its own host (api.cimplify.io) on a different path prefix than the storefront API.

SurfaceHostPath prefix
Storefrontstorefronts.cimplify.io/api/v1/*
Linkapi.cimplify.io/v1/link/*

Authentication is via a customer session token (Authorization: Bearer …), established by the OTP flow at /v1/link/auth/*.

Conventions

  • POST for updates. POST /v1/link/<resource>/:id updates an existing record with a partial body. Both create and update share the verb; payload distinguishes them.
  • Response envelope: same as the storefront API: { success: true, data: … } on 2xx, { success: false, error: { code, message, retryable } } on errors.
  • Idempotency: pass Idempotency-Key: <uuid> on writes that must replay safely.

Auth

POST /v1/link/auth/request-otp

curl -X POST https://api.cimplify.io/v1/link/auth/request-otp \
  -H "Content-Type: application/json" \
  -d '{"contact":"+233244000000","contact_type":"phone"}'
{ "success": true, "message": "OTP sent" }

POST /v1/link/auth/verify-otp

curl -X POST https://api.cimplify.io/v1/link/auth/verify-otp \
  -H "Content-Type: application/json" \
  -d '{
    "contact":"+233244000000",
    "contact_type":"phone",
    "otp_code":"123456"
  }'
{
  "success": true,
  "session_token": "lnk_…",
  "refresh_token": "rfr_…",
  "account_id":   "acc_…",
  "customer_id":  "cus_…",
  "message": "Verified"
}

Carry Authorization: Bearer lnk_… on every subsequent call.

POST /v1/link/auth/logout

curl -X POST https://api.cimplify.io/v1/link/auth/logout \
  -H "Authorization: Bearer lnk_…"

Profile + data

GET /v1/link/data

The one-shot read most callers want: customer + addresses + mobile money + preferences in one trip.

curl https://api.cimplify.io/v1/link/data \
  -H "Authorization: Bearer lnk_…"
{
  "customer": { "id":"cus_…", "name":"Jane Doe", "email":"jane@example.com", "phone":"+233244000000", ... },
  "addresses": [ ... ],
  "mobile_money": [ ... ],
  "preferences": { ... },
  "default_address": { ... } | null,
  "default_mobile_money": { ... } | null
}

POST /v1/link/check-status

Look up whether a contact is already enrolled in Link, before asking for OTP.

curl -X POST https://api.cimplify.io/v1/link/check-status \
  -H "Content-Type: application/json" \
  -d '{"contact":"+233244000000"}'
{ "is_link_customer": true }

Enrollment

POST /v1/link/enroll

Idempotency-keyed. Once enrolled, calling again returns the existing enrollment.

curl -X POST https://api.cimplify.io/v1/link/enroll \
  -H "Authorization: Bearer lnk_…" \
  -H "Idempotency-Key: 91f3…" \
  -d '{"contact":"+233244000000","name":"Jane Doe"}'

POST /v1/link/enroll-and-link-order

Atomic enrollment + order attachment. Useful in a guest-checkout flow that wants to upgrade the customer in the same transaction as the order finalization.

curl -X POST https://api.cimplify.io/v1/link/enroll-and-link-order \
  -H "Authorization: Bearer lnk_…" \
  -d '{
    "order_id": "ord_…",
    "business_id": "bus_…",
    "address": { "label":"Home", "street_address":"…", "city":"Accra", "region":"GA" },
    "mobile_money": { "phone_number":"+233244000000", "provider":"mtn", "label":"My MTN" },
    "order_type": "delivery"
  }'

Preferences

GET /v1/link/preferences

Returns NOT_ENROLLED if the customer hasn't enrolled.

POST /v1/link/preferences

Partial update. Sends only the fields you want to change.

curl -X POST https://api.cimplify.io/v1/link/preferences \
  -H "Authorization: Bearer lnk_…" \
  -d '{
    "preferred_order_type":"delivery",
    "default_address_id":"addr_…",
    "notify_on_order": true
  }'

Addresses

GET /v1/link/addresses

Returns an array of CustomerAddress.

POST /v1/link/addresses

curl -X POST https://api.cimplify.io/v1/link/addresses \
  -H "Authorization: Bearer lnk_…" \
  -H "Idempotency-Key: 4a82…" \
  -d '{
    "label":"Home",
    "street_address":"12 Independence Ave",
    "apartment":"Flat 3",
    "city":"Accra",
    "region":"Greater Accra",
    "country":"GH"
  }'

The first address you create is marked is_default: true automatically.

POST /v1/link/addresses/:id

Partial update.

DELETE /v1/link/addresses/:id

POST /v1/link/addresses/:id/default

Promote to default. Server clears is_default on all other addresses atomically.

POST /v1/link/addresses/:id/track-usage

Increment usage_count and stamp last_used_at. Used by checkout to surface "frequently used" addresses.

Mobile money

GET /v1/link/mobile-money

Array of CustomerMobileMoney.

POST /v1/link/mobile-money

curl -X POST https://api.cimplify.io/v1/link/mobile-money \
  -H "Authorization: Bearer lnk_…" \
  -d '{
    "phone_number":"+233244000000",
    "provider":"telecel",
    "label":"My main account"
  }'

provider accepts: mtn, vodafone, telecel, airtel, airteltigo, mpesa (plus a few legacy spellings). The server normalises to the short codes Paystack expects (mtn / vod / atl / mpesa) when constructing payment requests.

DELETE /v1/link/mobile-money/:id

POST /v1/link/mobile-money/:id/default

POST /v1/link/mobile-money/:id/track-usage

POST /v1/link/mobile-money/:id/verify

Run the provider's verification flow to confirm the phone number is reachable on the chosen network.

Sessions

GET /v1/link/sessions

Array of LinkSession records.

DELETE /v1/link/sessions

Revoke every session for this customer. Returns { success: true, revoked: <n> }.

DELETE /v1/link/sessions/:id

Revoke one specific session.

Element-iframe variants

/v1/link/elements/* is a parallel auth surface for the Cimplify Link iframes hosted at link.cimplify.io. It exists because cross-origin iframes can't use cookies the same way the SDK does; Elements use a postMessage-based token bootstrap and explicit refresh. Storefront-side SDK code uses the regular /v1/link/auth/* flow.

/v1/link/auth/*/v1/link/elements/*
Used by@cimplify/sdk client.link.*link.cimplify.io/elements/* iframes
AuthAuthorization: BearerpostMessage init + explicit refresh
RefreshImplicit (cookies)POST /v1/link/elements/refresh

Contract snapshots

All Link wire shapes are emitted as JSON Schema by the backend contract pipeline. The TS-side companion in packages/sdk/scripts/diff-contracts.ts compares against the SDK's zod-derived schemas to catch drift at PR time.

Covered types:

  • CustomerAddress, CustomerMobileMoney, CustomerLinkPreferences, LinkData, LinkCustomer
  • RequestOtpRequest, VerifyOtpRequest, LinkAuthResponse
  • CreateAddressRequest, UpdateAddressRequest, CreateMobileMoneyRequest
  • EnrollRequest, EnrollAndLinkOrderRequest, UpdatePreferencesRequest

On this page