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.
| Surface | Host | Path prefix |
|---|---|---|
| Storefront | storefronts.cimplify.io | /api/v1/* |
| Link | api.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>/:idupdates 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 |
| Auth | Authorization: Bearer | postMessage init + explicit refresh |
| Refresh | Implicit (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,LinkCustomerRequestOtpRequest,VerifyOtpRequest,LinkAuthResponseCreateAddressRequest,UpdateAddressRequest,CreateMobileMoneyRequestEnrollRequest,EnrollAndLinkOrderRequest,UpdatePreferencesRequest
Related
- SDK: client.link: the typed TS surface
- Cimplify Link overview: the embedded iframe Elements
- Testing: createTestClient: Link mock parity
Support
Customer-facing support / chat-widget API. Each session has exactly one widget conversation; the server resolves it from the session identity, so callers never have to track a `conversation_id`.
Inventory
Stock and availability lookups, scoped per location. Inventory is tracked at either the product or the variant level depending on the product’s `inventory_type`.