# Authentication Source: https://docs.lomi.africa/api/authentication All lomi. requests are authenticated using API keys. Any request that doesn't include a valid API key will return an authentication error. *** title: 'Authentication' description: "All lomi. requests are authenticated using API keys. Any request that doesn't include a valid API key will return an authentication error." --------------------------------------------------------------------------------------------------------------------------------------------------------- For a guided path from your first `curl` to the OpenAPI pages, SDKs, and webhook caveats, see **[API integration](/start/first-payment)**. There are two types of secret API keys corresponding to lomi.'s two environments: Test and Live. ## API keys All API requests must be made over HTTPS. Requests made over plain HTTP will fail. API requests without authentication will also fail. Send your **secret** key on every merchant REST request. Publishable keys (`lomi_pk_test_...` / `lomi_pk_live_...`) are for browser or mobile card flows only. Do not send them as `X-API-Key`. Accepted headers (case-insensitive): * `X-API-Key` (or `X-API-KEY`) * `X-Lomi-API-Key` * `Authorization: Bearer ` ```http filename="API request header" GET /accounts/balance HTTP/1.1 Host: api.lomi.africa X-API-Key: your_api_key ``` Replace `your_api_key` with your secret API key. The compact request-header table (idempotency, scenario key, Network) lives on **[API keys](/start/api-keys#request-headers)**. ## Network account targeting Approved lomi. Network Operators can target a connected Member Account by adding the optional `Lomi-Account` header. ```http filename="Network request header" POST /checkout-sessions HTTP/1.1 Host: api.lomi.africa X-API-Key: your_operator_api_key Lomi-Account: acct_1234567890 ``` When `Lomi-Account` is absent, requests behave like normal merchant API calls. When it is present, the API key organization is the Operator and the `acct_...` account is the target Member Account. See **[lomi. Network](/build/platform/network)** for supported endpoints and required capabilities. ## Test vs. Live environments lomi. provides two distinct environments to separate development and testing from production operations. ### Test environment Use the test environment to develop and test your integration without processing real transactions or affecting live data. It mirrors the functionality of the live environment. * **API Keys:** Test API keys start with `lomi_sk_test_`. * **Base URL:** `https://sandbox.api.lomi.africa` * **Data:** Uses simulated data. Responses may include environment flags like `"environment": "test"`. To simulate card and mobile money payments (test card numbers, Wave, MTN), see **[Sandbox payments](/start/sandbox-payments)**. ### Live environment Use the live environment for production operations involving real transactions. * **API Keys:** Live API keys start with `lomi_sk_live_`. * **Base URL:** `https://api.lomi.africa` * **Data:** Processes real data. Responses may include environment flags like `"environment": "live"`. To switch between environments, use the matching API key and base URL. Rate limits are the same in both environments. ## Rate limits The same throttling policy applies to sandbox and live: | Scope | Limit | | ---------------------------------------------------------------------------------------------------------------- | ---------------------------- | | Default | 5000 requests per 15 minutes | | Writes (`POST /checkout-sessions`, `POST /payment-requests`, `POST /refunds`, `POST /payouts`, `POST /charge/*`) | 120 requests per minute | Successful responses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Window-Seconds`, and IETF `RateLimit` / `RateLimit-Policy`. A `429` includes `Retry-After` and `error.details.retry_after_seconds`. See **[Errors](/api/errors)**. ## Obtaining API keys 1. Log in to the [dashboard](https://dashboard.lomi.africa). 2. Open **Settings → Access tokens**. 3. Use separate secret keys for the **Test** and **Live** environments. ## Key management and security Treat your API keys as sensitive credentials. You can manage them (generate, revoke, view usage) in the merchant dashboard. Best practices: * **Keep keys confidential:** Do not share your secret keys. Store them securely (for example environment variables or a secrets manager). * **Never expose keys client-side:** Do not embed secret API keys in frontend code. * **Avoid version control:** Do not commit keys to your codebase. * **Use test keys for development:** Only use live keys for production applications. * **Rotate keys:** Rotate keys periodically or if you suspect a compromise. * **Limit access:** Restrict access to API keys within your organization. ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. # Data models Source: https://docs.lomi.africa/api/data-models Common objects returned by the public lomi. API, with the fields integrators usually need to understand first. *** title: 'Data models' description: 'Common objects returned by the public lomi. API, with the fields integrators usually need to understand first.' ----------------------------------------------------------------------------------------------------------------------------- This page explains the objects you will see across the merchant-facing API. It is not a dump of every internal schema. If an object only exists for dashboard administration, provider configuration, or lomi. operations, it is intentionally left out of the public reference. Use the endpoint pages for the exact request and response contract. Use this page when you want the shape and meaning of the objects you will pass around in your integration. ## Shared conventions | Field | Meaning | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `id`, `*_id` | Stable identifier for an object. Store it in your system when you need to reconcile or fetch the object later. | | `amount`, `gross_amount`, `net_amount`, `fee_amount` | Integer amount in the smallest currency unit. For `XOF`, `5000` means 5,000 XOF. | | `currency_code` | ISO currency code such as `XOF`, `USD`, or `EUR`. | | `metadata` | Your own key-value data. Use it for internal order IDs, user IDs, or reconciliation hints. | | `environment` | `test` or `live`. Never mix objects from both environments in the same reconciliation flow. | | `created_at`, `updated_at` | ISO 8601 timestamps. | ## Customer A customer represents the payer or buyer attached to payments, subscriptions, and portal sessions. Returned by [Customers](/api/customers) endpoints and embedded in some payment/subscription responses. ```json filename="Customer" { "customer_id": "cus_123", "name": "Awa Diop", "email": "awa@example.com", "phone_number": "+221771234567", "country": "SN", "metadata": { "crm_id": "lead_981" }, "environment": "live", "created_at": "2026-02-14T10:30:00.000Z", "updated_at": "2026-02-14T10:30:00.000Z" } ``` ## Product and price A product is what you sell. A price is the amount and billing behavior attached to that product. Returned by [Products](/api/products), checkout, and subscription flows. ```json filename="Product with prices" { "product_id": "prod_123", "name": "Pro plan", "description": "Monthly access to the Pro workspace", "product_type": "recurring", "is_active": true, "metadata": { "plan_code": "pro" }, "prices": [ { "price_id": "price_123", "amount": 15000, "currency_code": "XOF", "billing_interval": "month", "pricing_model": "standard", "is_default": true } ], "environment": "live" } ``` ## Checkout session A checkout session creates a hosted payment page. Your application redirects the customer to `checkout_url`, then listens for payment status through redirects and webhooks. Status is `open`, `completed`, or `expired`. Returned by [Checkout sessions](/api/checkout-sessions). ```json filename="Checkout session" { "checkout_session_id": "123e4567-e89b-12d3-a456-426614174000", "status": "open", "amount": 15000, "currency_code": "XOF", "checkout_url": "https://pay.lomi.africa/cs_TSASLZNKD7TE5F", "success_url": "https://example.com/success", "cancel_url": "https://example.com/cancel", "expires_at": "2026-02-14T11:30:00.000Z", "metadata": { "order_id": "order_481" } } ``` ## Payment link A payment link is a durable hosted URL you can share. The `url` is on `pay.lomi.africa`. Opening it creates a checkout session. Returned by [Payment links](/api/payment-links). ```json filename="Payment link" { "link_id": "plink_123", "title": "Pro plan", "amount": 15000, "currency_code": "XOF", "url": "https://pay.lomi.africa/ABCDEFGHJKLMNP", "is_active": true, "environment": "live" } ``` ## Direct charge A direct charge starts a provider-specific payment without hosted checkout. Use it only when you need to control the payment experience yourself. `POST /charge/wave` and `POST /charge/mtn` are the live rails. `POST /charge/card` and `POST /charge/switch` return `503 service_unavailable` until those rails are unmuted. Returned by [Advanced direct charges](/api/charge). ```json filename="Wave or MTN charge" { "success": true, "transaction_id": "123e4567-e89b-12d3-a456-426614174000", "status": "pending", "next_action": { "type": "redirect", "url": "https://pay.wave.com/c/abc123" } } ``` Wave may also return `wave_launch_url` or `checkout_url`. MTN wraps the transaction in `data` (`data.transaction_id`, `data.status`). Always follow `next_action` when present. ## Subscription A subscription represents recurring access for a customer. Status is `pending`, `active`, `past_due`, `cancelled`, `trial`, `paused`, or `expired`. Returned by [Subscriptions](/api/subscriptions). ```json filename="Subscription" { "subscription_id": "sub_123", "customer_id": "cus_123", "product_id": "prod_123", "price_id": "price_123", "status": "active", "start_date": "2026-02-01", "end_date": null, "next_billing_date": "2026-03-01", "metadata": { "workspace_id": "wk_42" } } ``` ## Transaction A transaction is the ledger-style record of money movement. Use it for reconciliation, reporting, and support investigations. Status is `pending`, `completed`, `failed`, `refunded`, `expired`, or `held`. See [Payment and payout lifecycle](/build/reliability/payment-lifecycle). Returned by [Transactions](/api/transactions). ```json filename="Transaction" { "transaction_id": "123e4567-e89b-12d3-a456-426614174000", "gross_amount": 5000, "fee_amount": 125, "net_amount": 4875, "currency_code": "XOF", "transaction_type": "payment", "status": "completed", "provider_code": "WAVE", "payment_method_code": "MOBILE_MONEY", "metadata": { "order_id": "order_481" }, "created_at": "2026-02-14T10:31:04.000Z" } ``` ## Refund A refund returns money against a completed transaction. Returned by [Refunds](/api/refunds). ```json filename="Refund" { "refund_id": "123e4567-e89b-12d3-a456-426614174000", "transaction_id": "123e4567-e89b-12d3-a456-426614174000", "refunded_amount": 5000, "status": "completed" } ``` ## Payout A payout moves funds from your lomi. balance. `kind` is `withdrawal` (to you) or `beneficiary` (to a third party). Status typically moves `pending` → `processing` → `completed` or `failed`. Returned by [Payouts](/api/payouts). ```json filename="Payout" { "success": true, "payout_id": "123e4567-e89b-12d3-a456-426614174000", "kind": "withdrawal", "status": "processing" } ``` ## Balance `GET /accounts/balance` returns the current amount by currency. Returned by [Balances](/api/balances). ```json filename="Balance" { "currency_code": "XOF", "balance": 275000, "last_updated": "2026-02-14T10:45:00.000Z" } ``` Available vs pending funds are on the breakdown endpoint (`available_balance`, `pending_balance`, `total_balance`). See [Balance and settlement](/build/money/balance-and-settlement). ## Webhook endpoint A webhook endpoint tells lomi. where to deliver events. The signing `secret` is returned on **create** only. List and get responses use `webhook_id` and do not include the secret. Returned by [Webhooks](/api/webhooks). ```json filename="Webhook endpoint (create)" { "webhook_id": "123e4567-e89b-12d3-a456-426614174000", "url": "https://example.com/webhooks/lomi", "authorized_events": ["PAYMENT_SUCCEEDED", "PAYMENT_FAILED"], "is_active": true, "secret": "whsec_...", "created_at": "2026-02-14T10:30:00.000Z" } ``` ## Webhook delivery log A delivery log is an operational record for one webhook attempt. Returned under [Webhooks](/api/webhooks). ```json filename="Webhook delivery log" { "log_id": "123e4567-e89b-12d3-a456-426614174000", "webhook_id": "123e4567-e89b-12d3-a456-426614174000", "event_type": "PAYMENT_SUCCEEDED", "success": false, "response_status": 500, "attempt_number": 3, "created_at": "2026-02-14T10:45:00.000Z" } ``` ## Error Errors return a machine-readable code, a human-readable message, and, when available, structured details. ```json filename="Error" { "error": { "code": "unauthorized", "message": "Invalid API key", "details": "The provided API key is invalid or does not exist" }, "request_id": "550e8400-e29b-41d4-a716-446655440000" } ``` See [Errors](/api/errors) for status codes and common failure patterns. ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. # Error handling Source: https://docs.lomi.africa/api/errors Understanding and handling API error responses. *** title: 'Error handling' description: 'Understanding and handling API error responses.' -------------------------------------------------------------- We use conventional HTTP status codes to indicate the success or failure of an API request. In general: * Codes in the `2xx` range indicate success. * Codes in the `4xx` range indicate a client-side error (for example a required parameter was omitted, or invalid data was sent). * Codes in the `5xx` range indicate an error with our servers (these are rare). In case of doubt, visit our [status page](https://status.lomi.africa). When an API request fails (returns a `4xx` or `5xx` status code), the response body contains a JSON object detailing the error. For how to parse these in your app with the TypeScript SDK, see [Error handling](/build/reliability/error-handling). ## Error response structure All error responses follow a consistent JSON format: ```json filename="Example of an error response" { "error": { "code": "unauthorized", "message": "A human-readable description of the error.", "details": "Optional: Additional details or structured information about the error." }, "request_id": "550e8400-e29b-41d4-a716-446655440000" } ``` | Property | Type | Description | | --------------- | -------------------- | ---------------------------------------------------------------------------------------------------- | | `error` | `object` | Container for the error information. | | `error.code` | `string` | Machine-readable code (see [Stable error codes](#stable-error-codes)). | | `error.message` | `string` | A brief, human-readable summary of the error. | | `error.details` | `string` or `object` | Optional. More specific context (validation failures, `retry_after_seconds`, conflicting resources). | | `request_id` | `string` (UUID) | Correlates the response with server logs. Also sent as the `X-Request-Id` response header. | ## HTTP status codes | Code | Status | Meaning | | ----- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `200` | OK | The request was successful. | | `201` | Created | The request was successful and a resource was created (for example creating a webhook). | | `204` | No Content | The request was successful but there is no representation to return (for example deleting a webhook). | | `400` | Bad Request | The request was unacceptable, often due to a missing required parameter or invalid data. | | `401` | Unauthorized | No valid API key provided. | | `403` | Forbidden | The API key does not have permissions to perform the request. | | `404` | Not Found | The requested resource does not exist. | | `409` | Conflict | The request conflicts with the current state of the resource (for example an idempotency key reused with a different body). | | `429` | Too Many Requests | Rate limits have been exceeded. | | `500` | Internal Server Error | Something went wrong on lomi.'s end (these are rare). | | `503` | Service Unavailable | Temporary unavailability, including muted rails such as `POST /charge/card` and `POST /charge/switch`. Retry later, or use hosted checkout. | ## Stable error codes | Code | Typical HTTP status | | -------------------------- | ------------------- | | `bad_request` | 400 | | `validation_failed` | 400 | | `amount_invalid` | 400 | | `invalid_cursor` | 400 | | `unauthorized` | 401 | | `forbidden` | 403 | | `not_found` | 404 | | `conflict` | 409 | | `idempotency_key_required` | 400 | | `idempotency_key_reused` | 409 | | `idempotency_in_progress` | 409 | | `rate_limit_exceeded` | 429 | | `api_access_suspended` | 403 | | `service_unavailable` | 503 | | `internal_error` | 500 | ## Rate limits The same policy applies to sandbox and live. See [Authentication](/api/authentication#rate-limits). | Scope | Limit | | ---------------------------------------------------------------------------------------------------------------- | ---------------------------- | | Default | 5000 requests per 15 minutes | | Writes (`POST /checkout-sessions`, `POST /payment-requests`, `POST /refunds`, `POST /payouts`, `POST /charge/*`) | 120 requests per minute | A `429` includes: * `Retry-After` (seconds) * `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Window-Seconds` * IETF `RateLimit` / `RateLimit-Policy` * `error.details.retry_after_seconds` and `error.details.limit` ## Common error messages The specific `message` and `details` vary. Typical patterns: **`400` Bad Request:** * `"code": "validation_failed"` with field-level `details` * `"code": "amount_invalid"` * `"code": "idempotency_key_required"` * `"code": "invalid_cursor"` **`401` Unauthorized:** * `"code": "unauthorized"`, `"message": "Invalid API key"` * `"code": "unauthorized"`, `"message": "Missing API key"` **`404` Not Found:** * `"code": "not_found"` **`409` Conflict:** * `"code": "idempotency_key_reused"`, `"message": "Idempotency-Key was reused with a different request payload"` * `"code": "idempotency_in_progress"`, `"message": "A request with this Idempotency-Key is already in progress"` **`429` Too Many Requests:** * `"code": "rate_limit_exceeded"`, `"message": "Too many requests"` **`500` Internal Server Error:** * `"code": "internal_error"`, `"message": "Internal server error"` **`503` Service Unavailable:** * `"code": "service_unavailable"` (maintenance, or a muted direct-charge rail) ## Handling errors 1. Check the HTTP status code. 2. Parse the JSON body for `4xx` or `5xx` responses. 3. Use `error.code`, `error.message`, and `error.details`. 4. Retry `429` and `5xx` with backoff. Do not retry other `4xx` responses until you fix the request. 5. Log `request_id` (body and `X-Request-Id` header) for support. ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. # Overview Source: https://docs.lomi.africa/api Use the public lomi. merchant API to accept payments, manage commerce objects, move money, and handle operational events. *** title: Overview description: 'Use the public lomi. merchant API to accept payments, manage commerce objects, move money, and handle operational events.' index: true ----------- The API reference documents the public merchant API: the endpoints a developer should use from a server-side integration. It intentionally does not expose internal provider, organization, merchant-admin, or agent services. If you are choosing an integration path, start with [Integration journey](/start/integration-journey) or [Which integration to choose?](/build/choose-integration). ## Base URLs | Environment | Base URL | Secret key | | ----------- | --------------------------------- | ------------------ | | Test | `https://sandbox.api.lomi.africa` | `lomi_sk_test_...` | | Live | `https://api.lomi.africa` | `lomi_sk_live_...` | Send your secret key in the `X-API-Key` header from your server. ## Accept payments | Goal | API | | ------------------------------------- | ------------------------------------------- | | Hosted checkout | [Checkout sessions](/api/checkout-sessions) | | Shareable payment URLs | [Payment links](/api/payment-links) | | Backend-created payment requests | [Payment requests](/api/payment-requests) | | Lower-level Wave, MTN, and card flows | [Advanced direct charges](/api/charge) | ## Manage commerce | Goal | API | | -------------------------------------------- | ----------------------------------- | | Customer records and portal launch sessions | [Customers](/api/customers) | | Products, prices, and catalogs | [Products](/api/products) | | Recurring billing and customer subscriptions | [Subscriptions](/api/subscriptions) | | Discount codes and coupon performance | [Coupons](/api/coupons) | ## Move and reconcile money | Goal | API | | ----------------------------------- | --------------------------------- | | Read available and pending balances | [Balances](/api/balances) | | Inspect payment records | [Transactions](/api/transactions) | | Create and inspect refunds | [Refunds](/api/refunds) | | Create and inspect payouts | [Payouts](/api/payouts) | ## Operate reliably | Goal | API | | ---------------------------------------------------------------- | ------------------------- | | Configure endpoints, test delivery, inspect logs, retry failures | [Webhooks](/api/webhooks) | ## Reference conventions * Guide pages explain product decisions and end-to-end flows. * API pages describe exact HTTP operations. * Direct charges are advanced; hosted checkout remains the default recommendation. * Use sandbox for development and live only after you follow [What to check before live?](/start/go-live). ## Contract rules * **Money:** public `amount` fields are integers in minor units. `10000` is 10,000 XOF, or 100.00 USD/EUR. Always send `currency_code`. * **Idempotency:** `Idempotency-Key` is required on `POST /payment-requests`, `POST /refunds`, `POST /payouts`, `POST /settlements/instant`, and `POST /charge/*`. It is optional on `POST /checkout-sessions`. See [Idempotency keys](/build/reliability/idempotency-keys). * **Lists:** query with `cursor` and `limit` (default 20, max 100). Responses use `{ object: "list", data, has_more, next_cursor, limit }`. * **Errors:** `{ error: { code, message, details }, request_id }`. See [Error handling](/api/errors). * **Rate limits:** successful responses include `X-RateLimit-Remaining` and IETF `RateLimit` / `RateLimit-Policy`. `429` includes `Retry-After`. * Routes stay unversioned. The OpenAPI `info.version` is the schema release. Send optional `Lomi-Version` to pin a known schema. ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. # Which integration to choose? Source: https://docs.lomi.africa/build/choose-integration Choose between hosted checkout, payment links, payment requests, direct charges, subscriptions, and ecommerce extensions. *** title: 'Which integration to choose?' description: 'Choose between hosted checkout, payment links, payment requests, direct charges, subscriptions, and ecommerce extensions.' docType: explanation -------------------- import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; Choose the simplest integration that gives your customer the right payment experience. You can combine multiple lomi. products later. ## Start here | If you need | Use | Why | | ---------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | | A complete checkout page | [Hosted checkout](/build/accept/checkout) | Fastest production path for apps and stores | | Checkout on your site (iframe) | [Embed checkout widget](/build/accept/embed-widget) | Full lomi. checkout UI without leaving your page | | Social selling on WhatsApp | [WhatsApp Commerce](/build/accept/whatsapp-commerce) | Connect WhatsApp in the dashboard and share payment links in chat | | A URL you can share anywhere | [Payment links](/build/accept/payment-links) | Good for invoices, services, events, and no-code sales | | A backend-created payment request | [Payment requests](/build/accept/payment-requests) | Good when your app controls the order flow | | Mobile money-first collection | [Mobile money](/build/mobile-money) | Good for Wave, MTN, and SPI-heavy flows | | Embedded card collection | [Cards](/build/payment-methods/cards) | Good when you own the UI and need card entry inside your app | | Recurring billing | [Subscriptions](/build/billing/subscriptions) | Good for SaaS, memberships, and recurring services | | Customer self-service (billing, subscriptions) | [Customer portal](/build/billing/customer-portal) | Hosted portal for payment history, plan changes, and cancellations | | Store plugin checkout | [E-commerce extensions](/build/ecommerce-extensions) | Good for WooCommerce, Magento, PrestaShop, Shopify, and Bubble | | Hosted product catalog | [Products](/build/billing/products) + [store.lomi.africa](https://store.lomi.africa) | Good when you want a lomi.-hosted shop without a third-party platform | | AI assistant / agent access | [MCP](/build/mcp) | Cursor, Claude Desktop, and other MCP clients calling the merchant API | | Typed API access in your code | [SDKs](/build/sdks) | TypeScript, Python, Go, and PHP clients with typed methods and error handling | | Terminal-first setup and testing | [CLI](/build/cli) | Authenticate, scaffold projects, create sandbox checkouts, and forward webhooks | | Compare channels by country | [Payment channels](/build/payment-channels) | Country × currency × channel matrix | | Server-side Wave / MTN / card | [Direct charges](/build/accept/direct-charges) | When you own the payment UI end-to-end | ## Product comparison | | Hosted checkout | Embed widget | Payment links | Payment requests | Direct charges | Subscriptions | E-commerce plugins | | ---------------------- | ---------------------- | ------------------- | ------------- | -------------------------- | --------------------- | ------------- | ------------------ | | **Dev effort** | Low | Low | Low | Medium | High | Medium | Low | | **UI control** | lomi.-hosted | iframe on your site | lomi.-hosted | Your app + hosted pay step | Full (you build UI) | Hosted signup | Store-native | | **Speed to market** | High | High | High | Medium | Low | Medium | High | | **Mobile money async** | Handled on hosted page | Same as hosted | Same | Same | You handle pending UX | Hosted flow | Plugin-dependent | | **Webhooks required** | Recommended | Recommended | Recommended | Recommended | Required (live) | Required | Recommended | | **No-code dashboard** | Sessions + catalog | - | Yes | Partial | - | Plans + links | Yes | ## Recommended first choice Most teams should start with hosted checkout. It keeps sensitive collection flows on lomi., supports multiple payment methods, and gives you a production-ready customer flow without building payment UI from scratch. Use direct charges or embedded elements only when your product needs full control over the payment interface. ## What must every integration include? Every serious integration should include: * Test and live environment separation. * Server-side secret key usage. * A customer-visible success, cancel, pending, and failure state. * Webhook handling for final reconciliation. * Idempotency for create-style payment operations when retries are possible. Before go-live, run [Simulate errors](/build/reliability/simulate-errors) in sandbox to exercise failure and pending states. Make a test payment Hosted checkout Verify payments API reference # How to accept mobile money? Source: https://docs.lomi.africa/build/mobile-money Use hosted checkout, payment links, or direct charge APIs to accept Wave, MTN, SPI, and local payment methods. *** title: 'How to accept mobile money?' description: 'Use hosted checkout, payment links, or direct charge APIs to accept Wave, MTN, SPI, and local payment methods.' ----------------------------------------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; Mobile money lets you accept payments from customers using their mobile wallets. It is a fast, secure, and convenient payment method that does not require a bank account. For country and channel coverage, see [Payment channels](/build/payment-channels). ## Requirements Before integrating mobile money payments: 1. A lomi. account with **test** or **live** API keys, see [API keys](/start/api-keys). 2. An HTTPS **webhook** endpoint for final payment status in live mode, see [Webhooks](/build/reliability). 3. Customer **phone number** in E.164 format (for example `+2250707070707`). ## How mobile money payments work When a customer selects mobile money, they typically receive a prompt on their registered device. To complete the transaction, the customer must: 1. Open the notification or payment screen on their mobile device. 2. Authorize the payment by **entering their PIN** or following the provider's authentication process. 3. Once authorized, the payment is processed, and both the customer and the merchant receive a confirmation. After authorization: * The customer's mobile money wallet is debited. * lomi. sends a webhook notification to your server when the transaction reaches a final state. Mobile money is **asynchronous in live mode**. Always show a pending state in your UI and reconcile with webhooks or `GET /transactions/{id}`. ## Recommended paths | Path | Use when | | -------------------------------------------------- | ------------------------------------------------------------------ | | [Hosted checkout](/build/accept/checkout) | Your app creates the order and redirects the customer | | [Payment links](/build/accept/payment-links) | You want a shareable URL for invoices, services, or informal sales | | [Payment requests](/build/accept/payment-requests) | Your backend creates a payment request for an app-controlled flow | | [Direct charge APIs](/build/accept/direct-charges) | You need lower-level control and understand provider-specific UX | Most merchants should use **hosted checkout** or **payment links** first. ## Three recipes Each recipe ends the same way: wait for the webhook, then `GET /transactions/{id}`. See [Verify payments](/build/reliability/verify-payments). ### 1. Hosted Wave 1. Create a checkout session with `currency_code: "XOF"`. 2. Redirect the customer to `checkout_url`. 3. The customer selects Wave and approves in the Wave app. 4. Handle `PAYMENT_SUCCEEDED` / `PAYMENT_FAILED`, then `GET /transactions/{id}` before fulfilling. ### 2. Direct Wave (launch URL) 1. `POST /charge/wave` with XOF and customer `name` plus `phoneNumber`. 2. Redirect or deep-link to `wave_launch_url` or `checkout_url` (`next_action.type: redirect`). 3. In live, status stays pending until the customer pays in Wave. 4. Webhook plus `GET /transactions/{id}` before fulfilling. ### 3. Direct MTN (push) 1. `POST /charge/mtn` with amount, currency, `countryCode`, and MSISDN. 2. The customer gets a prompt on the phone (`next_action.type: await_webhook`). There is no redirect. 3. In live, status stays `PENDING` until PIN approval. 4. Webhook plus `GET /transactions/{id}` before fulfilling. ## Direct API: Wave `POST /charge/wave` with XOF amount and customer details. Redirect the customer to `wave_launch_url` or `checkout_url` in the response, or read the normalized **`next_action`** field (`type: redirect` with `url`). ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/wave" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "currency": "XOF", "customer": { "name": "Jane Doe", "email": "jane@example.com", "phoneNumber": "+2250707070707" }, "description": "Invoice #42", "successUrl": "https://example.com/success", "errorUrl": "https://example.com/error" }' ``` ## Direct API: MTN `POST /charge/mtn` with amount, currency, `countryCode`, and customer MSISDN. The response includes **`next_action`** (`type: await_webhook` with `status`) alongside `data.status`. ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/mtn" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "currency": "XOF", "countryCode": "CI", "customer": { "name": "Jane Doe", "phoneNumber": "+2250707070707" } }' ``` ## Verify the payment Before you provide value to the customer, confirm the transaction's final status and amount: * **Webhooks**: register your endpoint and handle `PAYMENT_SUCCEEDED` / `PAYMENT_FAILED`. See [Handling webhooks](/build/reliability/handling-webhooks). * **Retrieve transaction**: `GET /transactions/{id}` using the `transaction_id` from the charge response. ## Test vs live | | Test API key | Live API key | | ------------------- | ------------------------------------------------------ | ----------------------------------------- | | MTN direct charge | `status: completed` immediately; test balance credited | `status: PENDING` until customer approves | | Wave | Test balance may credit when session is created | Wait for webhook or transaction poll | | Real wallet debited | Never | Yes | Full test instructions: [Sandbox payments: mobile money](/start/sandbox-payments#testing-mobile-money). Payment channels Direct charges Balance and settlement Refunds # Which payment methods? Source: https://docs.lomi.africa/build/payment-channels Country, currency, and channel matrix for hosted checkout and direct charges in francophone West Africa and MTN markets. *** title: 'Which payment methods?' description: 'Country, currency, and channel matrix for hosted checkout and direct charges in francophone West Africa and MTN markets.' --------------------------------------------------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; Use this page to confirm which rails and currencies you can offer before you pick an integration path. For per-channel flows, see the [payment channel guides](/build/payment-methods/wave). For the decision tree, see [Which integration to choose?](/build/choose-integration). Exact availability depends on your organization's enabled providers in the dashboard. Contact support if a channel you need is not active. ## Channel capabilities \[#channel-capabilities] Same columns on each rail page. Empty cells (`-`) mean we do not publish a number, not that the rail is unlimited. | Channel | Approval | Settlement | Direct vs hosted | Refund | Min / max | Guide | | ------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------- | --------------------------------------------------- | --------- | -------------------------------------- | | Wave | Redirect or launch URL (`wave_launch_url` / `checkout_url`) | Test: often when the charge is created. Live: after the customer approves in Wave, then `available_at` | Hosted and `POST /charge/wave` | Completed Wave transactions | - | [Wave](/build/payment-methods/wave) | | MTN | Push / PIN on the customer's phone (`next_action: await_webhook`) | Test: often immediate `completed`. Live: stays `PENDING` until approve, then `available_at` | Hosted and `POST /charge/mtn` | Completed MTN transactions (live refunds are async) | - | [MTN](/build/payment-methods/mtn-momo) | | SPI | Hosted: operator, USSD, or app on the checkout page | After the hosted payment completes, then `available_at` | Hosted only | Not listed on `POST /refunds` | - | [SPI](/build/payment-methods/spi) | | Cards | Hosted 3DS when the issuer requires it | After `completed`; some card setups delay `available_at` | Hosted. `POST /charge/card` returns `503` | Completed card transactions | - | [Cards](/build/payment-methods/cards) | **Default:** hosted checkout or payment links. Direct charges when you own the payment UI and understand rail-specific async behavior. ### Not lomi. rails | Channel | Status | | ------------ | ------------------------------------ | | Orange Money | Not a lomi. rail | | Apple Pay | Not a lomi. rail | | Djamo | Coming for Côte d'Ivoire and Senegal | **Djamo** support for Côte d'Ivoire and Senegal is coming soon. ## Markets and currencies | Region / focus | Primary currencies | Mobile money | Cards | | ------------------------------- | -------------------------------------------------- | ------------ | ------------------- | | UEMOA (Wave-first) | `XOF` | Wave | Yes | | MTN countries (see table below) | `XOF`, `XAF`, `GHS`, `UGX`, `ZMW`, `NGN`, `ZAR`, … | MTN | Yes (where enabled) | | International cards | `USD`, `EUR` (product-dependent) | - | Yes | ## MTN: supported countries \[#mtn-momo-supported-countries] Hosted checkout and `POST /charge/mtn` support these MTN target environments: | Country | Dial prefix | MTN target environment | | ------------------ | ----------- | ---------------------- | | Côte d'Ivoire (CI) | `+225` | `mtnivorycoast` | | Cameroon (CM) | `+237` | `mtncameroon` | | Ghana (GH) | `+233` | `mtnghana` | | Uganda (UG) | `+256` | `mtnuganda` | | Zambia (ZM) | `+260` | `mtnzambia` | | Benin (BJ) | `+229` | `mtnbenin` | | Congo (CG) | `+242` | `mtncongo` | | Eswatini (SZ) | `+268` | `mtnswaziland` | | Guinea (GN) | `+224` | `mtnguineaconakry` | | South Africa (ZA) | `+27` | `mtnsouthafrica` | | Liberia (LR) | `+231` | `mtnliberia` | | Nigeria (NG) | `+234` | `mtnnigeria` | Pass `countryCode` (ISO 3166-1 alpha-2) on direct MTN charges when the payer is outside CI. Default is `CI`. ## Wave Wave direct charges require **XOF**. The customer completes payment in the Wave app via `wave_launch_url` or `checkout_url` in the API response. In **test** mode, the ledger may credit your test balance when the session is created. In **live** mode, wait for webhooks or poll `GET /transactions/{id}` before fulfilling the order. ## Cards Sandbox test numbers and 3D Secure scenarios are listed in [Sandbox payments](/start/sandbox-payments). Use test keys only with test card numbers. ## Test vs live Environment is determined by your **API key** (`lomi_sk_test_...` vs `lomi_sk_live_...`), not the hostname alone. | | Test | Live | | ----------------- | --------------------------------- | ----------------------------------------- | | API base | `https://sandbox.api.lomi.africa` | `https://api.lomi.africa` | | MTN direct charge | `status: completed` immediately | `status: PENDING` until customer approves | | Webhooks | `environment: "test"` | `environment: "live"` | Mobile money Direct charges Sandbox payments Providers API # Community Source: https://docs.lomi.africa/resources/community We believe that the best products are built together with users' and contributors' input. In our case, our growth and evolution are essentially driven by real merchant performance and needs. *** title: 'Community' description: "We believe that the best products are built together with users' and contributors' input. In our case, our growth and evolution are essentially driven by real merchant performance and needs." ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ## Feedbacks **Help us build francophone West Africa's most comprehensive and developer-friendly payment processing platform!** ### Share your experience * Report bugs and suggest features on [GitHub](https://github.com/lomiafrica/lomi./issues) * Help improve our [documentation](https://github.com/lomiafrica/lomi./tree/main/apps/docs/) * Star our project on [GitHub](https://github.com/lomiafrica/lomi./) to help us reach more developers and chat with us on [socials](https://x.com/lomiafrica) ### Build with us * Contribute to our [open-source platform](https://github.com/lomiafrica/lomi./) * Create and share plugins/extensions/features/designs/logic, anything * Help other developers in the community * Submit pull requests ### Stay connected * Read our [blog](https://lomi.africa/blog) for updates and to learn more about us * Attend our community events (stay tuned!) * Follow us on [LinkedIn](https://www.linkedin.com/company/lomiafri/) ## Support channels ### For developers * Access developer support information: [Support](/start/support) * Need integration assistance? You can contact us directly at: [hello@lomi.africa](mailto:hello@lomi.africa) ### For business * For pre-sales questions, partnership inquiries, or other information before using lomi., contact us at: [hello@lomi.africa](mailto:hello@lomi.africa) You can also learn more about how our fees apply [here](/start/merchant-of-record/pricing) or interact with [our pricing tool](https://lomi.africa/pricing). # Manifesto Source: https://docs.lomi.africa/resources/manifesto The time has come to open-source payment processing (and software in general) across Africa. *** title: 'Manifesto' description: 'The time has come to open-source payment processing (and software in general) across Africa.' ----------------------------------------------------------------------------------------------------------- We believe that accepting payments shouldn't be complex, regardless of where you operate or what payment methods your customers prefer. The fragmented payment landscape of our region creates unnecessary barriers and friction for businesses trying to grow beyond national borders. We aim to address these challenges with a new approach to payment processing. We believe that: * Every venture, regardless of its size, should easily be able to sell online with a few lines of code and zero technical and administrative burden. * Builders should focus on growing their business, not wrestling with payment. * Innovation in financial services must be accessible to all. * Payment solutions must be built by and for local ecosystems. ## Our principles To achieve this vision, we've built lomi. on these three foundational principles: ### Merchant first * Your success is our success. * Every feature we build solves one of your needs. * Pricing is transparent and fair. * Your feedback drives our roadmap. ### Local context matters * We build specifically for West African markets, starting with Côte d'Ivoire. * We strive to listen and understand local payment behaviors to adapt to local realities. * We respect the partners who trust us and operate with a high level of execution and integrity. ### Community matters * Open source is more than just code sharing; it is a mindset, and it is in our blood. * Community contributions at every stage of our development process are welcome, valued, and recognized. * Knowledge sharing is essential to our success. By making our platform open-source, we aim to: * Foster transparency and trust with our builders and partners. * Encourage your contributions to continuously enhance the platform for everyone. * Enable developers to extend and customize integrator tooling (SDKs, CLI, plugins) to suit their unique needs. * Publish more of our stack over time while keeping production payments on lomi. today. * Work toward a supported self-hosted deployment path as more of the monorepo opens, for operators who bring their own provider agreements and compliance (not available yet). Join us in our mission to simplify payment processing and to be the foundational open-source platform powering seamless payments for ventures across francophone West Africa by 2030. # Open source Source: https://docs.lomi.africa/resources/open-source lomi. publishes integrator tooling and documentation as open source. Payment processing runs on our hosted platform, not as a self-hosted product today. *** title: 'Open source' description: 'lomi. publishes integrator tooling and documentation as open source. Payment processing runs on our hosted platform, not as a self-hosted product today.' ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; lomi. follows a **hosted-first** model. Merchants integrate via our cloud API, dashboard, and checkout. We are **not** offering self-hosted payment processing at this time. Our **long-term direction** is to open the monorepo progressively, more apps, more source, and eventually a supported path for operators who self-host with their own provider agreements and compliance. That future state is not available yet; see [Roadmap](#roadmap) for what is public today. We still publish source for transparency and for integrator tooling (SDKs, CLI, plugins, docs). In line with that vision, we follow a **dual-license approach**. Our project is primarily licensed under the **GNU Affero General Public License (AGPL)**, allowing the open-source community to freely use, modify, and redistribute our software. However, the AGPL requires that anyone using the software, especially when providing it as a service, **must also make the source code available to the public**. Therefore, if you wish to use **lomi.** without sharing your modifications, i.e. incorporating it into closed-source products, or use it commercially to offer similar products or services, **a commercial license is required**. Certain components are covered exclusively by our **commercial license**, designed for businesses that need additional features or usage flexibility. This **dual-licensing model** lets us serve both the open-source community and enterprise partners. See the monorepo [LICENSE](https://github.com/lomiafrica/lomi.?tab=License-1-ov-file) file for the complete terms of both licenses. ## What is open source today? | Area | Availability | | :------------------------------------------------------------- | :----------------------------------------------------- | | Hosted payment platform (API, dashboard, checkout, compliance) | ✅ Production, [sign up](https://dashboard.lomi.africa) | | API and webhooks | ✅ Hosted only | | SDKs, CLI, MCP, plugins | ✅ Integrator tooling (source + packages) | | Documentation website | ✅ Open source (`apps/docs`) | | Merchant dashboard, checkout, storefront | 🔒 Hosted product (source opening progressively) | | Admin | 🔒 Proprietary | **Not self-hostable today.** Running payment processing requires lomi.’s hosted infrastructure, payment partner agreements, compliance coverage, and operational stack. Local clones are for **development and contribution**, not for operating your own payment processor. ## Hosted platform For production payments, use the hosted platform on [dashboard.lomi.africa](https://dashboard.lomi.africa). You get built-in payment channels, payment partner agreements and compliance coverage, automatic updates, and dedicated support. ### Quick comparison | Feature | Open-source tooling | Hosted platform | | :------------------------------ | :--------------------------: | :-------------: | | API and webhooks | Integrate against hosted API | ✅ | | Built-in payment channels | - | ✅ | | Merchant dashboard and checkout | - | ✅ | | SDKs, CLI, MCP, plugins | ✅ Source + packages | ✅ | | Self-hosting the payment stack | On the roadmap | - | | Automatic updates | - | ✅ | | Dedicated support | Community | ✅ | ### Support options The hosted platform includes: * 24/7 technical support * Priority issue resolution * Implementation assistance ### Resources * [Monorepo on GitHub](https://github.com/lomiafrica/lomi.) * [Contributing](/resources/contributing) * [Manifesto](/resources/manifesto) * [Community](/resources/community) * [Status page](https://status.lomi.africa) ## Roadmap We are progressively open-sourcing the monorepo. **Self-hosting the full payment stack is a long-term objective**, not something we support in production today. The path looks like: publish more source → document operator requirements (providers, partner agreements, compliance) → offer a supported self-host option when the stack is complete and safe to run without lomi.’s hosted services. What is open today: * **Currently open source**: * Documentation website: **`apps/docs`** (this site) * CLI (**`apps/cli`**), integrator tool; build on the hosted API, not platform ops * API service: **`apps/api`** * SDKs: **`apps/sdks`** * E-commerce plugins: separate repos under `apps/plugins/`, [woo](https://github.com/lomiafrica/woo), [magento](https://github.com/lomiafrica/magento), [prestashop](https://github.com/lomiafrica/prestashop), [shopify](https://github.com/lomiafrica/shopify), [bubble](https://github.com/lomiafrica/bubble) * MCP server (**`apps/mcp`**), integrator tool; merchant API in your IDE, not platform ops * Agent plugin (**`apps/tools/agent-plugin`**), marketplace packaging for hosted MCP; [lomiafrica/agent-plugin](https://github.com/lomiafrica/agent-plugin) * Events boilerplate (separate repo): **[lomiafrica/events](https://github.com/lomiafrica/events/)** * **Opening soon**: * Merchant dashboard: **`apps/dashboard`** * **Proprietary**: * Admin dashboard: **`apps/admin`** * **Hosted product (source not fully public yet)**: * Checkout: **`apps/checkout`** * Storefront: **`apps/storefront`** * Customer portal: **`apps/customers`** * Marketing website: **`apps/website`** Many files, especially database migrations and core payment logic, are marked `/* @proprietary license */` even inside AGPL-licensed apps. ## Monorepo layout ```text filename="Project structure" └── lomi./ ├── README.md ├── CONTRIBUTING.md ├── LICENSE ├── pnpm-workspace.yaml ├── tooling/ # shared eslint, knip, tsconfig, postcss, scripts (not a package) ├── packages/ │ ├── ui/ # @lomi./ui — shared React primitives (workspace-internal) │ ├── shared/ # @lomi./shared — format helpers, json-value, generated Database types │ ├── queries/ # @lomi./queries — injected RPC wrappers │ ├── pay/ # @lomi./pay — hosted checkout/storefront form chrome (React 19) │ └── receipt-pdf/ # @lomi./receipt-pdf — receipt PDF (workspace-internal) └── apps/ ├── admin/ # Internal admin (proprietary) ├── api/ # NestJS payment API (open source) ├── checkout/ # Hosted checkout (hosted product) ├── cli/ # Rust CLI (integrator tool; open source) ├── customers/ # Customer portal (hosted product) ├── dashboard/ # Merchant dashboard (opening soon) ├── design/ # Shared design assets ├── docs/ # Developer documentation (open source, this app) ├── jumbo/ # lomi. Pos merchant phone app ├── mcp/ # MCP server (integrator tool; open source) ├── plugins/ # WooCommerce, Magento, PrestaShop, Shopify, Bubble, references ├── sdks/ # TypeScript, Python, PHP, Go SDKs (open source) ├── storefront/ # Merchant storefront (hosted product) ├── tools/ # Independent tools (agent-plugin, doctool, gsc-mcp, …) └── website/ # Marketing site (hosted product) ``` ## Merchant dashboard (`apps/dashboard`) The dashboard is the merchant back-office (transactions, payment links, settings, analytics). It is a **hosted product** today; source is opening progressively. High-level layout: ```text filename="Merchant dashboard (apps/dashboard)" └── apps/dashboard/ ├── package.json ├── .env.example ├── src/ # React app (Vite) ├── supabase/ # Canonical DB migrations and edge functions │ └── migrations/ └── public/ # Static assets ``` Database migrations under `supabase/migrations/` are the schema source of truth for the platform. They are not a recipe for running your own payment stack without lomi.’s hosted services and provider agreements. ## Contributing We welcome contributions to open-source areas and documentation. Open an issue or submit a pull request on the [lomi. monorepo](https://github.com/lomiafrica/lomi.). Before contributing: 1. Read our [Code of Conduct](https://github.com/lomiafrica/lomi.?tab=coc-ov-file) 2. Review our [Security Policy](https://github.com/lomiafrica/lomi.?tab=security-ov-file) 3. Do not modify files marked with `/* @proprietary license */` See [Contributing](/resources/contributing) and the monorepo [CONTRIBUTING.md](https://github.com/lomiafrica/lomi./blob/master/CONTRIBUTING.md). ## Need help deciding? If you have questions about open source vs the hosted platform, contact [hello@lomi.africa](mailto:hello@lomi.africa). To learn more about our vision, visit our [blog](https://lomi.africa/blog) or use the [pricing tool](https://lomi.africa/pricing). ## Useful links * **Discord**: [Discord community](https://discord.gg/yb4FnBmh) * **X**: [@lomiafrica](https://twitter.com/lomiafrica) * **GitHub**: [Issues](https://github.com/lomiafrica/lomi./issues) and [pull requests](https://github.com/lomiafrica/lomi./blob/master/CONTRIBUTING.md) * **Email**: [hello@lomi.africa](mailto:hello@lomi.africa) # Where are my API keys? Source: https://docs.lomi.africa/start/api-keys Find your test and live keys, understand which key belongs where, and store them safely. *** title: 'Where are my API keys?' description: 'Find your test and live keys, understand which key belongs where, and store them safely.' ------------------------------------------------------------------------------------------------------- API keys connect your application to lomi. Use test keys while building, then switch to live keys only when your integration is verified. ## Where are the keys? Open the dashboard and go to **Settings -> Access tokens** or the equivalent developer/API settings page for your account. You will see separate keys for test and live environments: | Key | Prefix | Use it from | | -------------------- | ------------------ | -------------------------------------- | | Secret test key | `lomi_sk_test_...` | Your server while building | | Secret live key | `lomi_sk_live_...` | Your server in production | | Publishable test key | `lomi_pk_test_...` | Browser/mobile card flows in test mode | | Publishable live key | `lomi_pk_live_...` | Browser/mobile card flows in live mode | ## Which key should I use? Use secret keys only on servers, scripts, workers, and CI jobs you control. Never expose a secret key in frontend code, mobile apps, public repositories, logs, screenshots, or support messages. Use publishable keys only when a guide explicitly asks for them, such as embedded card confirmation with Payment Elements. ## Environment variables Store keys in your runtime environment: ```bash filename=".env.example" LOMI_SECRET_KEY=lomi_sk_test_xxxxxxxxxxxxxxxxxxxxxx LOMI_PUBLISHABLE_KEY=lomi_pk_test_xxxxxxxxxxxxxxxxxxxxxx LOMI_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxx ``` When you go live, replace test keys with live keys and point raw HTTP calls at `https://api.lomi.africa`. ## Verify the key works ```bash curl -sS \ -H "X-API-Key: $LOMI_SECRET_KEY" \ "https://sandbox.api.lomi.africa/accounts" ``` If the request returns `401`, check that you copied the full secret key and that your server is loading the environment variable. ## Request headers \[#request-headers] Send these on merchant REST calls. Header names are case-insensitive. | Header | Required | When | | ---------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `X-API-Key` (or `X-API-KEY`) | Yes | Every merchant request. Alias: `x-lomi-api-key`. `Authorization: Bearer ` is also accepted. | | `Content-Type` | On JSON bodies | `application/json` for POST/PATCH with a body | | `Idempotency-Key` | On money-moving writes | Required on `POST /payment-requests`, `POST /refunds`, `POST /payouts`, `POST /settlements/instant`, and `POST /charge/*`. Recommended on `POST /checkout-sessions` (optional so existing merchant apps can create a hosted page without it). See [Idempotency keys](/build/reliability/idempotency-keys). | | `X-Scenario-Key` | No | Sandbox **direct** Wave/MTN only (`pending` or `failed`). Hosted checkout and cards ignore it. See [Simulate errors](/build/reliability/simulate-errors). | | `Lomi-Account` | No | lomi. Network: operator key targeting a member `acct_...`. See [Network](/build/platform/network). | Responses include `X-Request-Id` (also `request_id` on error bodies). You do not send a tracing header. Make a test payment API authentication Errors # Get started with the CLI Source: https://docs.lomi.africa/start/cli-quickstart Install lomi. CLI, log in, run quickstart checks, create a test checkout, and forward webhooks locally. *** title: 'Get started with the CLI' description: 'Install lomi. CLI, log in, run quickstart checks, create a test checkout, and forward webhooks locally.' ---------------------------------------------------------------------------------------------------------------------- The **lomi. CLI** is the fastest way to authenticate, create test checkouts, listen for webhooks without ngrok, and install AI agent rules, all from your terminal. ### Install the CLI Requires **Node.js 18+** (used only to download the native binary). ```bash filename="Terminal" npm install -g lomi.cli lomi --version ``` See [CLI installation](/build/cli) for Homebrew and source builds. ### Log in ```bash filename="Terminal" lomi login ``` This opens a browser device-authorization flow and saves a CLI token globally. See [Authenticate](/build/cli/auth) for profiles and CI tokens. ### Run quickstart ```bash filename="Terminal" lomi quickstart ``` Checks API connectivity, identity, and balance, then prints the recommended next commands. Use `--json` for machine-readable output: ```bash filename="Terminal" lomi quickstart --json ``` ### Create a test checkout (headless) ```bash filename="Terminal" lomi checkout create \ --amount 10000 \ --currency XOF \ --success-url https://example.com/success \ --cancel-url https://example.com/cancel \ --json ``` Open the `checkout_url` from the JSON response in your browser and pay with a [sandbox test method](/start/sandbox-payments). ### Forward webhooks locally In a second terminal: ```bash filename="Terminal" lomi listen http://localhost:3000/webhooks ``` Sandbox-first, no ngrok required. See [Listen for webhooks](/build/cli/listen). ### Install AI agent rules ```bash filename="Terminal" lomi install-rules ``` Installs Cursor, Claude Code, Codex, and `llms.txt` rules so coding agents understand lomi. APIs. See [Install rules](/build/cli/install-rules). ## What to run next | Goal | Command | | ------------------------ | ---------------------------------------------------------------- | | Full integration check | `lomi probe` | | List recent transactions | `lomi transactions list --json` | | Refund a transaction | `lomi refunds create --transaction-id --amount 5000 --json` | | Scaffold a project | `lomi init` | | Local webhook server | `lomi dev` | Full reference: [Command reference](/build/cli/commands). ## Two kinds of credentials **`lomi login`** stores a **CLI token**: used by CLI commands like `checkout create`, `listen`, and `quickstart`. **`lomi init`** writes your **secret API key** (`LOMI_SECRET_KEY`) into the project `.env`, used by the **SDK in your application code**. These are different credentials for different purposes. Make a test payment Initialize a project Choose an integration # How do I create an account? Source: https://docs.lomi.africa/start/create-account Create your dashboard account, choose the right verification path, and prepare your business to accept test and live payments. *** title: 'How do I create an account?' description: 'Create your dashboard account, choose the right verification path, and prepare your business to accept test and live payments.' --------------------------------------------------------------------------------------------------------------------------------------------- Create your lomi. account from the dashboard, complete onboarding, and start in test mode before moving real money. ## Before you start Have these details ready: * Your name, email, phone number, country, and business use case. * A government-issued ID for a Starter account. * Business registration and proof of address if you are onboarding as a registered business. ## Which account path should I choose? | Path | Best for | Typical review | | -------- | ----------------------------------------------------------------------- | ------------------------------------------- | | Starter | MVPs, indie projects, early sales, and validation | Usually fast once identity is complete | | Business | Registered businesses that need higher limits and production operations | Manual review after documents are submitted | Start with the path that matches your current legal and operational reality. You can upgrade when your activity grows. ## Create your account 1. Open [dashboard.lomi.africa](https://dashboard.lomi.africa). 2. Sign up with email or a supported social account. 3. Complete your profile and business details. 4. Upload the requested identity or business documents. 5. Wait for verification to complete. 6. Use test mode while your integration is being built. ## What happens after verification? After your account is ready, you can: * Get test and live API keys. * Create checkout sessions and payment links. * Configure webhook endpoints. * Invite teammates from the dashboard. * Prepare to go live. Find your API keys Make a test payment Check before going live # How do I test a payment? Source: https://docs.lomi.africa/start/first-payment Create a sandbox checkout session, open the hosted checkout URL, pay with a test method, and verify the result. *** title: 'How do I test a payment?' description: 'Create a sandbox checkout session, open the hosted checkout URL, pay with a test method, and verify the result.' ------------------------------------------------------------------------------------------------------------------------------ This is the fastest way to prove your lomi. account, API key, checkout, and webhook assumptions work together. ## Before you start You need: * A lomi. account. * CLI access via `lomi login`, or a test secret key such as `lomi_sk_test_...`. * A local or hosted success URL and cancel URL. ## Option A: CLI (recommended) ### 1. Install and log in ```bash filename="Terminal" npm install -g lomi.cli lomi login lomi quickstart ``` ### 2. Create a checkout session Interactive: ```bash filename="Terminal" lomi checkout create ``` Headless (for scripts and agents): ```bash filename="Terminal" lomi checkout create \ --amount 10000 \ --currency XOF \ --success-url https://example.com/success \ --cancel-url https://example.com/cancel \ --json ``` Open the `checkout_url` from the output in your browser. ### 3. Forward webhooks (optional) ```bash filename="Terminal" lomi listen http://localhost:3000/webhooks ``` See [CLI quickstart](/start/cli-quickstart) for the full terminal workflow. ## Option B: cURL ### 1. Create a checkout session ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/checkout-sessions" \ -H "X-API-Key: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency_code": "XOF", "title": "Test order", "success_url": "https://example.com/success", "cancel_url": "https://example.com/cancel" }' ``` The response includes a hosted checkout URL. Open it in your browser. ## Option C: SDK Create the same sandbox checkout from application code with `@lomi./sdk`. The full install, first call, checkout, and webhook-verify steps are in [Get started with the SDK](/start/sdk-quickstart). Other languages: [Python](/build/sdks/python), [Go](/build/sdks/go), [PHP](/build/sdks/php). ## Option D: MCP Ask Cursor, Claude Desktop, or another MCP client to create a checkout session (`lomi_checkout`, `action=create`). Connect with OAuth or an API key. See [MCP for AI clients](/build/mcp). ## 2. Pay with a test method For a simple successful card payment, use: | Field | Value | | ----------- | ------------------------------------ | | Card number | `4242 4242 4242 4242` | | Expiry | Any future date, for example `12/34` | | CVC | Any three digits | You can also test mobile money methods in sandbox. See [sandbox payments](/start/sandbox-payments) for the full behavior. ## 3. Verify the result After payment, confirm: * The checkout redirects to your success URL. * The transaction appears in the dashboard test view. * Your test balance changes, if the method creates a completed test transaction. * Your webhook endpoint receives the event, if webhooks are configured. Verify from the CLI: ```bash filename="Terminal" lomi transactions list --json ``` Hosted checkout Payment links Webhooks API reference # What to check before live? Source: https://docs.lomi.africa/start/go-live Review keys, checkout URLs, webhooks, payment methods, reconciliation, and support before enabling real payments. *** title: 'What to check before live?' description: 'Review keys, checkout URLs, webhooks, payment methods, reconciliation, and support before enabling real payments.' -------------------------------------------------------------------------------------------------------------------------------- Review these points before switching from test keys to live keys. ## Account and dashboard * Your account verification is complete. * Business details, support contact, and payout information are correct. * Team members have the right access. * Test data is not confused with live reporting. ## Keys and environments * Production servers use `lomi_sk_live_...`. * Frontend card flows use `lomi_pk_live_...` only where required. * Secret keys are stored in environment variables or a secrets manager. * Logs, analytics, and error reporters do not capture keys or `client_secret` values. ## Checkout and payment methods * Success and cancel URLs point to production pages. * Checkout copy, product names, amounts, currencies, and metadata are correct. * Enabled payment methods match the countries and customers you serve. * Failure, cancellation, and pending states are visible to customers. ## Webhooks and reconciliation * Your webhook endpoint is reachable over HTTPS. * Signatures are verified from the raw request body. * Event handling is idempotent. * Your system can reconcile orders against lomi. transaction IDs. * Your team knows where to inspect webhook delivery and retry behavior. ## Final live test Create one small live payment, complete it, verify the webhook, confirm dashboard reporting, then refund or reconcile it according to your internal process. ## Agent-provisioned merchants (MCP / Partner API) If an AI agent onboarded your business via the Partner API or MCP: 1. Build and test with `lomi_sk_test_*` immediately after onboarding completes. 2. When ready for real payments, the agent calls `POST /provisioning/merchants/{id}/live-activation/request` and shares the **merchant approval link** with you. 3. Open `/connect/go-live` on the dashboard, review the request, and approve go-live yourself. 4. **Starter businesses**: KYC is reviewed automatically (AI-assisted, capped at 650,000 XOF gross live volume before full KYB). 5. **Registered businesses**: a platform admin reviews your business in `admin.lomi.africa` → Provisioning → Go-live. 6. After approval, retrieve your **live secret key** on the go-live page. Your agent never receives live credentials via the provisioning API. Next: [API reference](/api) or [Build guides](/build/choose-integration). # Integration journey Source: https://docs.lomi.africa/start/integration-journey Step-by-step path from sandbox account to live payments: keys, integration choice, testing, webhooks, and go-live. *** title: 'Integration journey' description: 'Step-by-step path from sandbox account to live payments: keys, integration choice, testing, webhooks, and go-live.' docType: tutorial ----------------- import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; Hosted checkout, payment links, subscriptions, and the other products below all go through the same merchant API. The choice of **what to build** is independent of **how you use lomi.**: API, SDK, CLI, or MCP. ## In sandbox you can * Pay with [test cards](/start/sandbox-payments) * Let Wave and MTN auto-complete on test keys * Send `X-Scenario-Key: pending` or `failed` on direct Wave/MTN charges * Forward webhooks with `lomi listen` ## In live you cannot * Auto-complete Mobile Money (the customer must approve on device) * Use test PANs (they only work with test keys) ## How to use lomi. Pick the surface that matches how you work. Use the API when you want the exact request and response, a language without an official SDK, or to compare against OpenAPI. Send your secret key as `X-API-Key` (`LOMI_SECRET_KEY`). `Idempotency-Key` is required on money-moving writes. Create a sandbox checkout: ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/checkout-sessions" \ -H "X-API-Key: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency_code": "XOF", "title": "Test order", "success_url": "https://example.com/success", "cancel_url": "https://example.com/cancel" }' ``` Open `checkout_url` from the response and pay with a [sandbox test method](/start/sandbox-payments). Full first-payment path: [Make a test payment](/start/first-payment). Endpoint details: [Create checkout session](/api/checkout-sessions/CheckoutSessionsController_create). The **lomi. SDK** is the fastest way to call the lomi. API from your application code, create checkout sessions, manage customers and subscriptions, issue refunds, and verify webhooks, with typed methods and built-in error handling. This guide uses the **TypeScript SDK** (`@lomi./sdk`). The same flow applies to [Python](/build/sdks/python), [Go](/build/sdks/go), and [PHP](/build/sdks/php). Install with `npm install @lomi./sdk`, set `LOMI_SECRET_KEY` in `.env`, then: ```typescript const session = await lomi.checkoutSessions.create({ amount: 10000, currency_code: 'XOF', title: 'Premium subscription', success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', }); console.log('Redirect to:', session.checkout_url); ``` `lomi init` can write the SDK client, example files, and your `.env` automatically. Full walkthrough: [Get started with the SDK](/start/sdk-quickstart). Language references: [SDKs](/build/sdks). The **lomi. CLI** is the fastest way to authenticate, create test checkouts, listen for webhooks without ngrok, and install AI agent rules, all from your terminal. Requires **Node.js 18+** (used only to download the native binary): ```bash filename="Terminal" npm install -g lomi.cli lomi login lomi quickstart lomi checkout create \ --amount 10000 \ --currency XOF \ --success-url https://example.com/success \ --cancel-url https://example.com/cancel \ --json ``` Open the `checkout_url` from the JSON response. Forward webhooks with `lomi listen http://localhost:3000/webhooks`. **`lomi login`** stores a CLI token. **`lomi init`** writes your secret API key (`LOMI_SECRET_KEY`) for the SDK. These are different credentials. Full walkthrough: [Get started with the CLI](/start/cli-quickstart). Command reference: [CLI](/build/cli). The **Model Context Protocol (MCP)** lets AI assistants (Cursor, Claude Desktop, custom agents) call the **lomi. merchant API** for you: create checkouts, list payments, debug webhooks, without writing every HTTP request by hand. Recommended for Cursor / Claude / VS Code: add the hosted MCP URL with **no API key**. OAuth-capable clients open **Connect with lomi.** in the browser: ```json { "mcpServers": { "lomi.": { "url": "https://mcp.lomi.africa/mcp" } } } ``` Then ask the client to create a checkout session (`lomi_checkout`, `action=create`). Merchant tools follow `lomi_` with a required `action`. In the dashboard: **Settings → Integrations → MCP**, then **Developers → API keys → Connect MCP**. Full guide: [MCP for AI clients](/build/mcp). ## Step 1: Create a developer account Sign up for a lomi. account to access the **test environment**, where you can run full payment flows without moving real money. In test mode you can: 1. Accept payments using test card numbers and simulated mobile money. 2. Create checkout sessions, payment links, and direct charges against the sandbox API. 3. Receive webhooks with `"environment": "test"` on payloads. See [Create your account](/start/create-account) and [API keys](/start/api-keys). ## Step 2: Choose your integration lomi. supports multiple integration options depending on your stack: | If you need | Start with | | ----------------------------------------------- | ------------------------------------------------------------------- | | A complete checkout page | [Hosted checkout](/build/accept/checkout) | | Checkout embedded on your site | [Embed checkout widget](/build/accept/embed-widget) | | A shareable URL | [Payment links](/build/accept/payment-links) | | A backend-created payment request | [Payment requests](/build/accept/payment-requests) | | Server-initiated mobile money or embedded cards | [Direct charges](/build/accept/direct-charges) | | Recurring billing | [Subscriptions](/build/billing/subscriptions) via checkout or links | | Store plugin (WooCommerce, Shopify, etc.) | [E-commerce extensions](/build/ecommerce-extensions) | Define what you sell in [Products](/build/billing/products) before you create checkout sessions or subscription plans. Most teams should start with **hosted checkout**. Use [Which integration to choose?](/build/choose-integration) and [Payment channels](/build/payment-channels) to confirm Wave, MTN, and card coverage for your markets. ## Step 3: Test end-to-end Test your integration thoroughly using sandbox credentials: 1. [Make a test payment](/start/first-payment): CLI or API checkout session. 2. Use [Sandbox payments](/start/sandbox-payments) for test cards, Wave, and MTN behavior. 3. Exercise failure paths with [Simulate errors](/build/reliability/simulate-errors). 4. Fix issues using [Error handling](/build/reliability/error-handling) and [Idempotency keys](/build/reliability/idempotency-keys). 5. For checkout edge cases (subscriptions, trials, pending mobile money), see [Checkout behavior](/build/accept/checkout-behavior) and per-channel guides under [Payment channels](/build/payment-channels#channel-capabilities). The same first checkout can be created from [REST](#call-api), [SDK](#call-sdk), [CLI](#call-cli), or [MCP](#call-mcp). The CLI and cURL paths are spelled out in [Make a test payment](/start/first-payment). The SDK and MCP paths are in [Get started with the SDK](/start/sdk-quickstart) and [MCP](/build/mcp). ## Step 4: Configure webhooks Webhooks are especially useful for payment methods and events that happen outside your application's control, such as mobile money approval and subscription renewals. 1. Register an HTTPS endpoint in the [dashboard](https://lomi.africa/portal). 2. Follow [Setting up webhooks](/build/reliability) and [Handling webhooks](/build/reliability/handling-webhooks). 3. Verify signatures from the raw request body before you parse JSON. **Subscription renewals:** Card subscriptions renew off-session; listen for `SUBSCRIPTION_RENEWED` and renewal `PAYMENT_FAILED`. **Wave and MTN subscriptions** use a **manual renewal checkout link** sent before each billing date, the customer must pay that link; there is no silent wallet debit. See [Subscriptions: Renewals](/build/billing/subscriptions#renewals-and-failed-payments). ## Step 5: Go live When sandbox flows work end to end: 1. Complete account verification and payout setup. 2. Switch servers to `lomi_sk_live_...` and `https://api.lomi.africa`. 3. Follow [What to check before live?](/start/go-live) and process one small live payment with webhook verification. Verify payments Go live Hosted checkout Webhooks # What is lomi.? Source: https://docs.lomi.africa/start/overview lomi. is a payment processing platform for francophone West African businesses in the UEMOA. One integration for cards, mobile money, checkout, billing, and payouts. *** title: 'What is lomi.?' description: 'lomi. is a payment processing platform for francophone West African businesses in the UEMOA. One integration for cards, mobile money, checkout, billing, and payouts.' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ lomi. is a payment processing platform for businesses selling in francophone West Africa. It gives you one place to accept card and mobile money payments, send payment links, run hosted checkout, manage customers and subscriptions, receive webhooks, and reconcile transactions and payouts. ## When should I use lomi.? Use lomi. when you want to: * Accept Wave, MTN, SPI, cards, and other local payment methods through one integration. * Send a customer to a hosted checkout instead of building payment collection from scratch. * Create payment links for invoices, services, events, donations, or product sales. * Run recurring payments and customer subscriptions. * Receive reliable webhook events for payments, refunds, subscriptions, and checkout states. * Track balances, transactions, refunds, and payouts from one dashboard. ## Payment complexity The West African payment landscape is fragmented across many providers. That often forces merchants to maintain multiple integrations or depend on closed-source processors, which increases development time, transaction cost, and maintenance overhead. lomi. addresses this with: * **APIs**: one integration for cards, MTN, Wave, SPI (64+ mobile money operators and banks), and more. * **Dashboard**: a web back-office to sell goods, services, subscriptions, and more. * **lomi. Pos**: the merchant phone app to sell on the go and withdraw earnings. * **Control**: reconciliation, refunds, payouts, hosted checkout, payment links, embeddable elements, direct charge APIs, and reporting. > **Get started in francophone West Africa by [creating an account](https://dashboard.lomi.africa).** ## Transparency and compliance lomi. is built for visibility into fees, transaction flows, and payout processing: * **Production**: payments run on lomi., [create an account](https://dashboard.lomi.africa). The stack is **not self-hostable today**. * **Open source**: integrator tooling and documentation are published progressively, see [open source](/resources/open-source). * **Community governance**: roadmap and product input via GitHub issues and pull requests. * **Compliance**: BCEAO licensing (in fieri), PCI DSS level 4, certified card partners, GDPR and MACP with data flows through Cape Town and Dublin. For the full product vision, see the [manifesto](/resources/manifesto). ## What can I build first? Most teams should start with one of these paths: | Goal | Best starting point | | ------------------------------------ | ---------------------------------------------------------------- | | Follow the full sandbox-to-live path | [Integration journey](/start/integration-journey) | | Compare API, SDK, CLI, and MCP | [How to use lomi.](/start/integration-journey#how-you-call-lomi) | | Get running from the terminal | [CLI quickstart](/start/cli-quickstart) | | Call the API from your app | [SDK quickstart](/start/sdk-quickstart) | | Let an AI assistant call lomi. | [MCP](/build/mcp) | | Accept a first online payment | [Make a test payment](/start/first-payment) | | Add checkout to an app or store | [Hosted checkout](/build/accept/checkout) | | Sell without writing checkout code | [Payment links](/build/accept/payment-links) | | Define your catalog before checkout | [Products](/build/billing/products) | | Compare every integration option | [Choose an integration](/build/choose-integration) | | Connect your backend to events | [Webhooks](/build/reliability) | | Let customers manage billing | [Customer portal](/build/billing/customer-portal) | | WooCommerce or Shopify store | [E-commerce extensions](/build/ecommerce-extensions) | ## How does the platform fit together? The typical flow is: 1. Create an account and complete onboarding. 2. Get your test API keys from the dashboard (or run `lomi login` for CLI access). 3. Create a checkout session with `lomi checkout create`, a payment link, or a payment request. 4. Let the customer pay with a supported method. 5. Confirm the result from the API, dashboard, or webhook. 6. Move to live keys when your integration is ready. The API reference has exact request and response details. The product guides explain which product to use and how to ship the flow. Create an account Integration journey Make a test payment Choose an integration # How do I test in sandbox? Source: https://docs.lomi.africa/start/sandbox-payments Use test keys, test payment methods, test balances, and webhook verification before switching to live mode. *** title: 'How do I test in sandbox?' description: 'Use test keys, test payment methods, test balances, and webhook verification before switching to live mode.' -------------------------------------------------------------------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; lomi. **test environment** lets you run full payment flows, hosted checkout, embedded card forms, and mobile money, without moving real money or touching live balances. This page is the **credentials and test-method reference**: which test card numbers to use, how Wave and MTN behave in test mode, and what to expect in your dashboard and webhooks. How to structure tests: [Testing guide](/build/reliability/testing). Scenario matrix (declines, pending Mobile Money, webhook failures): [Simulate errors](/build/reliability/simulate-errors). For API keys, base URLs, and rate limits, see **[Authentication](/start/api-keys)**. For your first authenticated request, see **[API integration](/start/first-payment)**. Test transactions never affect live balances, treasury, or payouts. Customer receipt emails and WhatsApp messages are not sent in test mode. Do not load-test the sandbox. Rate limits still apply; the sandbox is for correctness, not capacity. Sandbox cannot open a card dispute: there is no test trigger. Disputes only appear from live card-network events. See [Disputes](/build/money/disputes). ## Test environment at a glance | | Test | Live | | ------------------- | ------------------------------------ | ------------------------- | | **API base URL** | `https://sandbox.api.lomi.africa` | `https://api.lomi.africa` | | **Secret key** | `lomi_sk_test_…` | `lomi_sk_live_…` | | **Publishable key** | `lomi_pk_test_…` | `lomi_pk_live_…` | | **Balances** | Dashboard **test balance** only | Real merchant balance | | **Responses** | `"environment": "test"` on resources | `"environment": "live"` | Environment is determined by your **API key**, not the hostname: a test key always creates and reads test data, even if you call the production API host by mistake (the key still scopes you to test). ## Hosted checkout quick start End-to-end sandbox card payment in five steps (full recipes below in [API and hosted checkout recipes](#api-and-hosted-checkout-recipes)): 1. **Create a session**: `POST https://sandbox.api.lomi.africa/checkout-sessions` with `lomi_sk_test_…` (minimum body: `amount`, `currency_code`, `success_url`, `cancel_url`). See [Checkout sessions API](/api/checkout-sessions/CheckoutSessionsController_create). 2. **Open the URL**: response includes `checkout_url` (hosted payment page for that session). 3. **Pay with a test card**: select **Card**, enter `4242 4242 4242 4242`, expiry `12/34`, CVC `123` ([full test card list](#quick-reference--most-used-numbers) below). 4. **Complete**: transaction moves to `completed`; your **test balance** increases (not live funds). 5. **Verify**: dashboard **Test** mode and webhooks (`environment: "test"` on payloads). ```text POST /checkout-sessions → { "checkout_url": "https://…" } → customer pays → webhook + test balance ``` ## How test mode is chosen | What sets the environment | What happens next | | ------------------------------------ | ----------------------------------------------------------------------------------------------------- | | **Test or live API key** | Resources get `"environment": "test"` or `"live"` | | **Payment link or checkout session** | Hosted checkout uses the environment on that link or session | | **Same environment throughout** | **Test balance** vs live accounts; payments run as **card**, **Wave**, or **MTN** in that environment | **API** Every request authenticated with a test secret key runs in test mode. Created checkout sessions, payment intents, payment links, and transactions include `"environment": "test"`. **Hosted checkout** The environment on the **payment link** or **checkout session** controls the ledger and card form mode. Create links and sessions in **Test** mode in the [dashboard](https://lomi.africa/portal) so customers stay in the sandbox. **Dashboard** Toggle **Test / Live** in the portal. Payment links, QR codes, and catalog items created in test mode only appear in test reporting and test balances. ## Test balances and side effects When a test transaction reaches **`completed`**: * Your **test balance** increases (internal test ledger), not your live withdrawable balance. * Platform treasury and live channel floats are **not** updated. * Transaction metadata may include flags indicating a test ledger credit. Customer **emails** and **WhatsApp** notifications are skipped for test transactions. For how live balances work after completion, see **[Balance and settlement](/build/money/balance-and-settlement)**. ## Testing card payments Sandbox card payments use **test card numbers** that mimic real issuer behavior, approvals, declines, and authentication challenges, without charging anyone. Use them only with test API keys and test checkout sessions. Never enter real card details in test mode, and never use test numbers in production. Real cards in test are prohibited; test numbers in live will not work. ### How card payments complete in test | Flow | What you do | What lomi. does | | ------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | **Hosted checkout** | Open a test checkout URL → **Card** → enter a test number | Creates a **pending** transaction, then **completed** + test balance credit after successful card confirmation | | **Card charge API** | `POST /charge/card` with `lomi_sk_test_…` → confirm with `lomi_pk_test_…` on the client | Same: pending until confirmation, then completed and test balance credited | The card form on hosted checkout always matches the environment on the payment link or checkout session (test vs live). ### How to enter test cards When testing in the browser or in your app’s card form: * **Card number:** use a value from the tables below (spaces are optional; `4242 4242 4242 4242` and `4242424242424242` are equivalent). * **Expiry:** any **future** date, such as `12/34`. * **CVC:** any three digits for most brands; **four digits** for American Express test numbers. * **Cardholder name and billing fields:** any values your form accepts. To test **CVC validation failures**, you must enter a CVC. If you leave CVC empty, the check may be skipped and a “wrong CVC” test card will not behave as documented. ### Successful payments by card brand These numbers complete a standard charge in test mode when confirmation succeeds. | Brand | Card number | | --------------------- | --------------------- | | Visa | `4242 4242 4242 4242` | | Visa (debit) | `4000 0566 5556 5556` | | Mastercard | `5555 5555 5555 4444` | | Mastercard (2-series) | `2223 0031 2200 3222` | | Mastercard (debit) | `5200 8282 8282 8210` | | Mastercard (prepaid) | `5105 1051 0510 5100` | | American Express | `3782 822463 10005` | | Discover | `6011 1111 1111 1117` | | Diners Club | `3056 930009 02004` | | JCB | `3566 0020 2036 0505` | | UnionPay | `6200 0000 0000 0005` | For day-to-day QA, **`4242 4242 4242 4242`** (Visa) is the default choice. ### Declined and failed payments Use these numbers to verify error messages, failed checkout states, and webhook `PAYMENT_FAILED` handling. The transaction should **not** reach `completed` and your **test balance must not** increase. | Scenario | Card number | Typical result | | ------------------------- | --------------------- | ------------------------------------- | | Generic decline | `4000 0000 0000 0002` | Card declined | | Insufficient funds | `4000 0000 0000 9995` | Insufficient funds | | Lost card | `4000 0000 0000 9987` | Lost card | | Stolen card | `4000 0000 0000 9979` | Stolen card | | Expired card | `4000 0000 0000 0069` | Expired card | | Incorrect CVC | `4000 0000 0000 0127` | Incorrect CVC (enter any 3-digit CVC) | | Incorrect card number | `4242 4242 4242 4241` | Invalid number | | Processing error | `4000 0000 0000 0119` | Processing error | | Velocity limit exceeded | `4000 0000 0000 6975` | Velocity limit | | Decline after saving card | `4000 0000 0000 0341` | Save succeeds; later charges fail | See **[Errors](/api/errors)** for how failures appear in API responses. ### Strong Customer Authentication (3D Secure) Some test cards trigger an **authentication step** (redirect or modal) before the payment succeeds. Use these to test saved cards, subscriptions, and checkout flows that must handle “authenticate” vs “payment failed” outcomes. | Scenario | Card number | What to expect | | ---------------------------------------- | --------------------- | ---------------------------------------------------------------------- | | Authentication required (on-session) | `4000 0025 0000 3155` | Customer must complete authentication; succeeds after challenge | | Always requires authentication | `4000 0027 6000 3184` | Authentication on every payment | | Already set up for off-session | `4000 0038 0000 0446` | On-session may require auth; off-session can succeed without re-prompt | | Auth required, then insufficient funds | `4000 0082 6000 3178` | Auth may succeed; charge still declines for insufficient funds | | 3D Secure required (success) | `4000 0000 0000 3220` | Authentication required; payment succeeds after completion | | 3D Secure required (declined after auth) | `4000 0084 0000 1629` | Authentication required; payment declines after auth | | 3D Secure optional (success) | `4000 0000 0000 3055` | May authenticate; payment can succeed without challenge | | Frictionless 3D Secure | `4000 0000 0322 0000` | Authentication with frictionless success | | Not enrolled in 3D Secure | `4242 4242 4242 4242` | No challenge; ordinary successful Visa | | 3D Secure not supported (Amex) | `3782 822463 10005` | Payment proceeds without 3D Secure on this brand | Test authentication flows on your **hosted checkout** or **embedded card form**, not only via server-side API calls, so customers see the same challenge UI as in production. ### Quick reference: most used numbers | Goal | Use this number | | ------------------- | --------------------- | | Happy path | `4242 4242 4242 4242` | | Hard decline | `4000 0000 0000 0002` | | 3D Secure challenge | `4000 0025 0000 3155` | | Wrong CVC | `4000 0000 0000 0127` | | Insufficient funds | `4000 0000 0000 9995` | ### Embedded card forms For **[lomi Payment Elements](/build/accept/payment-elements)** or the Payment Intents API: create a payment intent with **`lomi_sk_test_…`**, mount the card UI with **`lomi_pk_test_…`**, and confirm using the returned **`client_secret`**. Use the test numbers above in the card field. ## Testing mobile money ### Wave In test mode, lomi. creates a **completed** transaction and credits your **test balance** as soon as the Wave checkout record is created. The customer may still see the Wave payment UI, but your dashboard test balance updates without waiting for a real wallet debit. **How to test:** 1. Create a **test** payment link or checkout session with Wave enabled. 2. Open hosted checkout and select **Wave**. 3. Enter a valid phone number in **E.164** format (for example `+225 07 00 00 00 00` for Côte d'Ivoire). 4. Complete or cancel on the Wave screen; verify the transaction and test balance in the dashboard. ### MTN In test mode, lomi. marks the MTN transaction **completed** and credits your **test balance** when the payment is initiated-**without calling the MTN sandbox**. No real MSISDN or sandbox credentials are required for the ledger flow. **Test refunds:** `POST /refunds` (or the dashboard refund dialog) on a completed test MTN transaction updates your test balance immediately. **No MTN Disbursement API call** is made in test mode. **How to test payments:** 1. Use a **test** payment link or session with MTN enabled. 2. Enter any valid MSISDN for your country (international format). 3. Confirm the transaction appears as completed in the dashboard test view. **Direct charge scenarios (`X-Scenario-Key`, test key only):** On `POST /charge/mtn` and `POST /charge/wave`, send `X-Scenario-Key: pending` to keep the charge in `PENDING` without auto-completing, or `failed` to simulate a `400` error. Omit the header for default test behavior. Hosted checkout and card charges do not read this header. ```bash # MTN: pending (no auto-complete) curl -sS -X POST "https://sandbox.api.lomi.africa/charge/mtn" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "X-Scenario-Key: pending" \ -H "Content-Type: application/json" \ -d '{"amount":1000,"currency":"XOF","customer_phone":"+2250700000000"}' # Wave: simulate failure curl -sS -X POST "https://sandbox.api.lomi.africa/charge/wave" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "X-Scenario-Key: failed" \ -H "Content-Type: application/json" \ -d '{"amount":1000,"currency":"XOF","customer_phone":"+2250700000000"}' ``` For live MTN integration and sandbox MSISDNs, see the [MTN developer portal](https://momodeveloper.mtn.com/). **Supported countries (hosted checkout)** | Country | Dial prefix | MTN target environment | | ------------------ | ----------- | ---------------------- | | Côte d'Ivoire (CI) | `+225` | `mtnivorycoast` | | Cameroon (CM) | `+237` | `mtncameroon` | | Ghana (GH) | `+233` | `mtnghana` | | Uganda (UG) | `+256` | `mtnuganda` | | Zambia (ZM) | `+260` | `mtnzambia` | | Benin (BJ) | `+229` | `mtnbenin` | | Congo (CG) | `+242` | `mtncongo` | | Eswatini (SZ) | `+268` | `mtnswaziland` | | Guinea (GN) | `+224` | `mtnguineaconakry` | | South Africa (ZA) | `+27` | `mtnsouthafrica` | | Liberia (LR) | `+231` | `mtnliberia` | | Nigeria (NG) | `+234` | `mtnnigeria` | ### Card vs mobile money in test | Method | When test balance credits | Real money moved | | --------- | ------------------------------------ | ---------------- | | **Cards** | After successful card confirmation | Never | | **Wave** | When the test transaction is created | Never | | **MTN** | When the test transaction is created | Never | ## Payouts in test mode `POST /payouts` with a **test API key** behaves as follows: | `rail` | `destination` | Test key | | ------------- | ----------------------- | -------------------------------------------------------------- | | `wave` | `self` or `beneficiary` | **`400`**: Wave payouts are live-only; no Wave API call | | `bank`, `spi` | `self` | Follows test withdrawal records where applicable | | `spi` | `beneficiary` | May create pending beneficiary records (no live Wave transfer) | Beneficiary **Wave** payouts require `recipient.name` and `recipient.phone` on a **live** key. The phone number does not need to match a registered `payout_method_id`. See **[Payouts](/build/money/payouts)**. `GET /payouts` with a test key returns **test withdrawals** only; live beneficiary payout rows are not included in the list. ## API and hosted checkout recipes ```bash curl -sS \ -H "X-API-Key: $LOMI_SECRET_KEY" \ "https://sandbox.api.lomi.africa/accounts" ``` ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/checkout-sessions" \ -H "X-API-Key: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "currency_code": "XOF", "title": "Sandbox test", "success_url": "https://example.com/success", "cancel_url": "https://example.com/cancel" }' ``` Open the returned `checkout_url` and pay with a test card or mobile money method. ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/card" \ -H "X-API-Key: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "currency_code": "XOF", "customer_email": "test@example.com", "customer_name": "Test User" }' ``` Use the returned `client_secret` with **`lomi_pk_test_…`** on your client. See **[Direct charges](/build/accept/direct-charges)** and **[lomi Payment Elements](/build/accept/payment-elements)**. Create payment links in **Test** mode in the dashboard, or via the API with your test key so `environment` is `test`. See **[Payment links](/build/accept/payment-links)**. ## Testing subscriptions Use **test** API keys and [Sandbox payments](/start/sandbox-payments) patterns below. 1. **Create a recurring product** with `trial_enabled: true` or `first_payment_type: non_initial` (see [Products](/build/billing/products)). 2. **Payment link or checkout session**: open hosted checkout or your storefront subscribe URL with the recurring `product_id`. 3. **Trial + card**: use `4242 4242 4242 4242`; checkout collects a card via SetupIntent and shows **$0** due today. Expect **`SUBSCRIPTION_CREATED`** when signup completes (not mid–card entry). 4. **Trial + Wave / MTN**: complete customer details and confirm; signup should succeed with **no charge** in test mode. 5. **Paid signup**: use `first_payment_type: initial` and complete a normal test payment; verify subscription `status` is `active` and a transaction of type `instalment` exists. 6. **Webhooks**: subscribe to `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, and `PAYMENT_FAILED` for renewal failures. Renewal cron behavior in test may still schedule future billing dates; use the dashboard or API to inspect `next_billing_date` rather than waiting for real time. ## Webhooks in test 1. In the dashboard (**Developers → Webhooks**), add an endpoint while in **Test** mode. 2. Use the **signing secret** shown for that test endpoint to verify signatures on the **raw request body**. 3. Successful test payments emit events such as **`PAYMENT_SUCCEEDED`** with `"environment": "test"` where applicable. 4. Use **Test webhook** in the dashboard to send a sample `PAYMENT_SUCCEEDED` payload without making a payment. See **[Webhooks](/build/reliability)** for subscription management and verification details. For automated webhook testing patterns, see the **[Testing guide](/build/reliability/testing)**. ## Troubleshooting | Symptom | Likely cause | What to try | | ----------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | Live data in API responses | Using `lomi_sk_live_…` | Switch to `lomi_sk_test_…` and `https://sandbox.api.lomi.africa` | | Card payment fails immediately | Decline test number or wrong CVC rule | Use `4242…` with any future expiry and any 3-digit CVC | | Card confirms but no test balance | Webhook not reaching your stack; wrong environment on session | Ensure test link/session; check dashboard transaction status | | Test key but checkout feels “live” | Payment link created in **Live** mode | Recreate link in **Test** mode (dashboard or with a test API key) | | Wave/MTN shows success, balance unchanged | Viewing **live** balance instead of **test** | Toggle dashboard to Test | | MTN live charge/refund errors | Missing provider reference or disbursement balance | Ensure payment completed with RequestToPay reference; check MTN disbursement wallet | | No customer email | Expected in test | Notifications are disabled for test transactions | ## When am I ready for live mode? Move to live keys only when your integration is verified end to end. See [What to check before live?](/start/go-live). Make a test payment Checkout sessions Webhooks Testing guide # Get started with the SDK Source: https://docs.lomi.africa/start/sdk-quickstart Install the lomi. SDK, set your API key, make your first sandbox call, create a test checkout, and verify webhooks. *** title: 'Get started with the SDK' description: 'Install the lomi. SDK, set your API key, make your first sandbox call, create a test checkout, and verify webhooks.' ---------------------------------------------------------------------------------------------------------------------------------- The **lomi. SDK** is the fastest way to call the lomi. API from your application code, create checkout sessions, manage customers and subscriptions, issue refunds, and verify webhooks, with typed methods and built-in error handling. This guide uses the **TypeScript SDK** (`@lomi./sdk`). The same flow applies to [Python](/build/sdks/python), [Go](/build/sdks/go), and [PHP](/build/sdks/php). ### Install the SDK ```bash filename="Terminal" npm install @lomi./sdk ``` Also available with `pnpm add`, `yarn add`, or `bun add`. See the [TypeScript SDK reference](/build/sdks/typescript) for other runtimes. ### Add your test API key Copy a **test secret key** (`lomi_sk_test_…`) from **Settings → Access tokens** and add it to a `.env` file. See [Access tokens](/start/api-keys). ```bash filename=".env" LOMI_SECRET_KEY=lomi_sk_test_xxxxxxxxxxxxxxxxxxxxxx ``` `lomi init` writes the SDK client, example files, and your `.env` automatically. See the [CLI quickstart](/start/cli-quickstart) and [Initialize a project](/build/cli/init). ### Make your first sandbox call Create `first-call.ts`, then fetch your sandbox balance to confirm the key works: ```typescript import { LomiSDK, LomiAuthError } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'test', // sandbox; use 'live' only in production }); async function main() { try { const balance = await lomi.accounts.getBalance(); console.log('Balance:', balance); } catch (error) { if (error instanceof LomiAuthError) { console.error(`Auth failed [${error.statusCode}]: ${error.message}`); process.exit(1); } throw error; } } main(); ``` ```bash filename="Terminal" npm install @lomi./sdk # load .env with your tooling (dotenv, Next.js, etc.) npx tsx first-call.ts ``` ### Create a test checkout ```typescript const session = await lomi.checkoutSessions.create({ amount: 10000, currency_code: 'XOF', title: 'Premium subscription', success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', }); console.log('Redirect to:', session.checkout_url); ``` Open `checkout_url` in your browser and pay with a [sandbox test method](/start/sandbox-payments). ### Verify a webhook Confirm events are authentic before trusting them, no hand-rolled HMAC required: ```typescript import { verifyWebhookSignature } from '@lomi./sdk'; const valid = verifyWebhookSignature( rawBody, // raw string or Buffer, not parsed JSON req.headers['x-lomi-signature'], process.env.LOMI_WEBHOOK_SECRET!, ); if (!valid) return res.status(401).send('Invalid signature'); ``` Forward events to your local server with the CLI, no ngrok required: ```bash filename="Terminal" lomi listen http://localhost:3000/webhooks ``` See [Listen for webhooks](/build/cli/listen) and [Webhooks](/build/reliability). ## What to build next | Goal | Reference | | ----------------------- | ---------------------------------------------------------------------------- | | Full TypeScript SDK API | [TypeScript SDK](/build/sdks/typescript) | | Other languages | [Python](/build/sdks/python) · [Go](/build/sdks/go) · [PHP](/build/sdks/php) | | Hosted checkout | [Checkout](/build/accept/checkout) | | Recurring billing | [Subscriptions](/build/billing/subscriptions) | | Error handling | [Error handling](/build/reliability/error-handling) | ## Two kinds of credentials The **SDK** uses your **secret API key** (`LOMI_SECRET_KEY`) in application code. The **CLI** uses a separate **CLI token** from `lomi login` for terminal commands like `checkout create` and `listen`. These are different credentials for different purposes. Make a test payment Choose an integration Go live # We're here for you Source: https://docs.lomi.africa/start/support Get help from the lomi. team: in-docs form, email, GitHub, and Discord. No public issues for security reports. *** title: "We're here for you" description: 'Get help from the lomi. team: in-docs form, email, GitHub, and Discord. No public issues for security reports.' ----------------------------------------------------------------------------------------------------------------------------- Use the form below for product or integration questions. For vulnerabilities, use the [security page](/build/reliability/security-best-practices) instead. ## Other channels * **Email:** [hello@lomi.africa](mailto:hello@lomi.africa) * **GitHub:** [questions](https://github.com/lomiafrica/lomi./issues/new?labels=question), [bugs](https://github.com/lomiafrica/lomi./issues/new?labels=bug), [features](https://github.com/lomiafrica/lomi./issues/new?labels=enhancement) * **Discord:** [community server](https://discord.gg/33syDfh9) * **Security:** [resources/security](/build/reliability/security-best-practices) or [security@lomi.africa](mailto:security@lomi.africa) ## Support coverage | Feature | End-customers | Merchants | Enterprises | | ------------------- | ------------- | --------- | ----------- | | Community support | - | ✓ | ✓ | | Email support | - | ✓ | ✓ | | Priority response | - | ✓ | ✓ | | Dedicated support | - | - | ✓ | | Enterprise features | - | - | ✓ | Pricing and fee schedules: [Pricing](/start/merchant-of-record/pricing) and [lomi.africa/pricing](https://lomi.africa/pricing). # Delete merchant account Source: https://docs.lomi.africa/api/account/AccountController_deleteAccount Delete merchant account *** title: "Delete merchant account" description: "Delete merchant account" full: true method: post path: /account/delete operationId: AccountController\_deleteAccount --------------------------------------------- ## Overview Delete merchant account Preview + confirmation\_token puis soft\_delete\_merchant. ### When to use this Use this endpoint when your flow needs `POST /account/delete`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /account/delete` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/account/delete" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `AccountController_deleteAccount` * **Operation**: `POST /account/delete` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Export account data Source: https://docs.lomi.africa/api/account/AccountController_export Export account data *** title: "Export account data" description: "Export account data" full: true method: post path: /account/export operationId: AccountController\_export -------------------------------------- ## Overview Export account data Bundle GDPR pour l’organisation courante. Pas de confirmation. ### When to use this Use this endpoint when your flow needs `POST /account/export`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /account/export` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/account/export" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `AccountController_export` * **Operation**: `POST /account/export` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create API key Source: https://docs.lomi.africa/api/api-keys/ApiKeysController_create Create API key *** title: "Create API key" description: "Create API key" full: true method: post path: /api-keys operationId: ApiKeysController\_create -------------------------------------- ## Overview Create API key Renvoie le secret une seule fois. Ne change pas la session MCP. ### When to use this Use this endpoint when your flow needs `POST /api-keys`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /api-keys` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/api-keys" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `ApiKeysController_create` * **Operation**: `POST /api-keys` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List API keys Source: https://docs.lomi.africa/api/api-keys/ApiKeysController_list List API keys *** title: "List API keys" description: "List API keys" full: true method: get path: /api-keys operationId: ApiKeysController\_list ------------------------------------ ## Overview List API keys Returns key name, type, prefix/last4, and status. Secret values are never returned. ### When to use this Use this endpoint when your flow needs `GET /api-keys` (List API keys). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /api-keys` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/api-keys" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `ApiKeysController_list` * **Operation**: `GET /api-keys` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Revoke API key Source: https://docs.lomi.africa/api/api-keys/ApiKeysController_remove Revoke API key *** title: "Revoke API key" description: "Revoke API key" full: true method: delete path: /api-keys/{id} operationId: ApiKeysController\_remove -------------------------------------- ## Overview Revoke API key Désactive la clé (valeur ou préfixe masqué). ### When to use this Use this endpoint when your flow needs `DELETE /api-keys/{id}`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `DELETE /api-keys/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X DELETE "https://sandbox.api.lomi.africa/api-keys/ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `ApiKeysController_remove` * **Operation**: `DELETE /api-keys/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Check available balance Source: https://docs.lomi.africa/api/balances/AccountsController_checkAvailableBalance Check available balance *** title: "Check available balance" description: "Check available balance" full: true method: get path: /accounts/balance/{currency} operationId: AccountsController\_checkAvailableBalance ------------------------------------------------------ ## Overview Check available balance Checks whether sufficient funds exist in the requested currency before you move money out or reserve balance. ### When to use this Call before initiating a payout, beneficiary payout, or any flow where you must guarantee spendable balance. ### See also [Account balances](/api/balances/AccountsController_getBalance) · [Payouts](/api/payouts/PayoutsUnifiedController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /accounts/balance/{currency}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/accounts/balance/currency_value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `AccountsController_checkAvailableBalance` * **Operation**: `GET /accounts/balance/{currency}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Account balances Source: https://docs.lomi.africa/api/balances/AccountsController_getBalance Account balances *** title: "Account balances" description: "Account balances" full: true method: get path: /accounts/balance operationId: AccountsController\_getBalance ------------------------------------------- ## Overview Account balances Returns current balances across currencies; optionally filter to one currency for simpler UI. ### When to use this Use for wallet surfaces, “available funds” displays, or pre-checking balances without fetching every account object. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /accounts/balance` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ---------- | ----- | -------- | ------ | --------------------------------------- | | `currency` | query | No | - | Filtrer par code devise (XOF, USD, EUR) | ## Responses | Status | Description | | ------ | --------------------- | | `200` | Informations de solde | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/accounts/balance?currency=XOF" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `AccountsController_getBalance` * **Operation**: `GET /accounts/balance` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Balance breakdown Source: https://docs.lomi.africa/api/balances/AccountsController_getBalanceBreakdown Balance breakdown *** title: "Balance breakdown" description: "Balance breakdown" full: true method: get path: /accounts/balance/breakdown operationId: AccountsController\_getBalanceBreakdown ---------------------------------------------------- ## Overview Balance breakdown Returns balance components (available, pending, totals) and may convert amounts into a target currency for reporting. ### When to use this Use when finance or support teams need a split between pending and available, not just a single number. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /accounts/balance/breakdown` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ----------------- | ----- | -------- | ------ | ----------------------------------------------- | | `target_currency` | query | No | - | Devise cible pour la conversion (XOF, USD, EUR) | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/accounts/balance/breakdown?target_currency=XOF" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `AccountsController_getBalanceBreakdown` * **Operation**: `GET /accounts/balance/breakdown` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Balances Source: https://docs.lomi.africa/api/balances Read balances and check whether funds are available before payout or reconciliation workflows. *** title: 'Balances' description: 'Read balances and check whether funds are available before payout or reconciliation workflows.' index: true ----------- Balances expose the money view a merchant integration may need for reconciliation and payout readiness. They replace the old top-level Accounts API in the public docs. ## When to use balances Use these endpoints when your backend needs to: * Display available and pending balances. * Check whether a payout amount is available. * Reconcile completed transactions against balance movement. * Build internal finance or operations views. For payout creation, see [Payouts](/api/payouts). ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. # Cancel embedded card charge Source: https://docs.lomi.africa/api/charge/ChargesController_cancelCardCharge Cancel embedded card charge *** title: "Cancel embedded card charge" description: "Cancel embedded card charge" full: true method: post path: /charge/card/{id}/cancel operationId: ChargesController\_cancelCardCharge ------------------------------------------------ ## Overview Cancel embedded card charge Cancels a card charge before completion. ### When to use this Use when the buyer abandons checkout. ### See also [Create card charge](/api/charge/ChargesController_createCardCharge) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /charge/card/{id}/cancel` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------------------- | | `id` | Yes | - | Card payment id (pi\_...) | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | --------------------- | | `200` | Card charge cancelled | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/card/value/cancel" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `ChargesController_cancelCardCharge` * **Operation**: `POST /charge/card/{id}/cancel` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create embedded card charge Source: https://docs.lomi.africa/api/charge/ChargesController_createCardCharge Create embedded card charge *** title: "Create embedded card charge" description: "Create embedded card charge" full: true method: post path: /charge/card operationId: ChargesController\_createCardCharge ------------------------------------------------ ## Overview Create embedded card charge Creates a card charge for embedded checkout and returns `client_secret` for client-side confirmation. ### When to use this Use for in-app card entry where you own the product UI and tokenization flow. ### Good to know Never log or expose `client_secret` publicly; treat it like a short-lived capability for the client SDK. ### See also [Create checkout session](/api/checkout-sessions/CheckoutSessionsController_create) if you prefer hosted card collection. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /charge/card` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `CreateCardChargeDto` | Field | Required | Type | Description | | ---------------------------- | -------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `amount` | Yes | `number` | Amount to charge in the original currency | | `currency_code` | No | `enum ("XOF", "USD", "EUR")` | Currency code | | `currency` | No | `enum ("XOF", "USD", "EUR")` | Backward-compatible alias for currency\_code. Use currency\_code in new integrations. | | `customer_id` | No | `string` | Internal customer UUID (v4). Alternative: send customer\_email + customer\_name to create/find a customer. | | `customer_email` | No | `string` | Customer email, required together with customer\_name when customer\_id is omitted. | | `customer_name` | No | `string` | Customer display name, required together with customer\_email when customer\_id is omitted. | | `customer_phone` | No | `string` | Customer phone number | | `description` | No | `string` | Description shown in payment providers and logs | | `payment_reference` | No | `string` | Reference included in metadata for reconciliation | | `product_id` | No | `string` | Optional product UUID for metadata and reconciliation | | `subscription_id` | No | `string` | Optional subscription UUID for metadata and reconciliation | | `quantity` | No | `number` | Optional quantity for internal reconciliation | | `metadata` | No | `object` | Custom metadata merged into provider metadata | | `appearance_theme` | No | `enum ("light", "dark", "flat")` | Optional Payment Element theme for client-side card UI: `light`, `dark`, or `flat`. | | `appearance_border_radius` | No | `number` | Optional Payment Element border radius (px) returned for client-side rendering. | | `appearance_billing_address` | No | `enum ("auto", "never")` | Optional Payment Element billing address collection mode. Use `never` to hide country/address selector in Payment Element UI. | Example body: ```json { "amount": 10000 } ``` ## Responses | Status | Description | | ------ | ------------------- | | `201` | Card charge created | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/card" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":10000}' ``` ## OpenAPI * **operationId**: `ChargesController_createCardCharge` * **Operation**: `POST /charge/card` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create MTN charge Source: https://docs.lomi.africa/api/charge/ChargesController_createMtnCharge Create MTN charge *** title: "Create MTN charge" description: "Create MTN charge" full: true method: post path: /charge/mtn operationId: ChargesController\_createMtnCharge ----------------------------------------------- ## Overview Create MTN charge Starts a payer-facing MTN RequestToPay charge. With a **test** API key the transaction completes in the ledger without calling the MTN sandbox. Responses include **`next_action`** (`await_webhook` with `status`) alongside `data.status`. ### When to use this Use for server-initiated MTN collection when you are **not** using a hosted checkout session. ### Good to know Live charges require MTN connected for your organization and a valid MSISDN. Refunds on live MTN payments use the Disbursement refund API via [Create refund](/api/refunds/RefundsController_create). ### See also [Mobile money](/build/mobile-money) · [Direct charges](/build/accept/direct-charges) · [Create Wave charge](/api/charge/ChargesController_createWaveCharge) · [Create refund](/api/refunds/RefundsController_create) · [Transactions](/api/transactions/TransactionsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /charge/mtn` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `CreateMtnChargeDto` | Field | Required | Type | Description | | ---------------- | -------- | ------------- | ----------- | | `amount` | Yes | `number` | - | | `currency` | Yes | `string` | - | | `organizationId` | No | `string` | - | | `merchantId` | No | `string` | - | | `customer` | Yes | `CustomerDto` | - | | `description` | No | `string` | - | | `countryCode` | No | `string` | - | | `productId` | No | `string` | - | | `subscriptionId` | No | `string` | - | | `quantity` | No | `number` | - | Example body: ```json { "amount": 1000, "currency": "XOF", "customer": { "name": "...", "phoneNumber": "..." } } ``` ## Responses | Status | Description | | ------ | -------------------- | | `201` | MTN charge initiated | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/mtn" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":1000,"currency":"XOF","customer":{"name":"...","phoneNumber":"..."}}' ``` ## OpenAPI * **operationId**: `ChargesController_createMtnCharge` * **Operation**: `POST /charge/mtn` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create Switch charge Source: https://docs.lomi.africa/api/charge/ChargesController_createSwitchCharge Create Switch charge *** title: "Create Switch charge" description: "Create Switch charge" full: true method: post path: /charge/switch operationId: ChargesController\_createSwitchCharge -------------------------------------------------- ## Overview Create Switch charge Authorizes a card from server-supplied credentials and routes it across acquiring rails. May return a 3DS redirect URL or signal `retry_other_rail` to fall back to another rail. ### When to use this Use when your integration is PCI-DSS compliant and submits card credentials server-side, rather than collecting cards through hosted checkout or embedded Payment Elements. ### Good to know Submitting raw card credentials requires a PCI-DSS-compliant integration. Follow `next_action` for 3DS redirects and `retry_other_rail` when the primary rail declines. ### See also [Create card charge](/api/charge/ChargesController_createCardCharge) · [Direct charges](/build/accept/direct-charges) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /charge/switch` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `CreateSwitchChargeDto` | Field | Required | Type | Description | | --------------------- | -------- | -------------- | ---------------------- | | `amount` | Yes | `number` | Amount in XOF francs | | `currency_code` | No | `enum ("XOF")` | - | | `pan` | Yes | `string` | - | | `expiry` | Yes | `string` | MM/YY or YYMM | | `cvv` | Yes | `string` | - | | `customer_id` | No | `string` | - | | `customer_email` | No | `string` | - | | `customer_name` | No | `string` | - | | `customer_phone` | No | `string` | - | | `description` | No | `string` | - | | `payment_reference` | No | `string` | - | | `product_id` | No | `string` | - | | `subscription_id` | No | `string` | - | | `checkout_session_id` | No | `string` | - | | `quantity` | No | `number` | - | | `metadata` | No | `object` | - | | `ecom_ip` | No | `string` | Customer IP for EComIp | Example body: ```json { "amount": 10000, "pan": "4221941234569109", "expiry": "06/25", "cvv": "123" } ``` ## Responses | Status | Description | | ------ | --------------------- | | `201` | Switch charge created | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/switch" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":10000,"pan":"4221941234569109","expiry":"06/25","cvv":"123"}' ``` ## OpenAPI * **operationId**: `ChargesController_createSwitchCharge` * **Operation**: `POST /charge/switch` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create direct mobile-money charge Source: https://docs.lomi.africa/api/charge/ChargesController_createWaveCharge Create direct mobile-money charge *** title: "Create direct mobile-money charge" description: "Create direct mobile-money charge" full: true method: post path: /charge/wave operationId: ChargesController\_createWaveCharge ------------------------------------------------ ## Overview Create direct mobile-money charge Starts a payer-facing mobile-money charge on a supported rail; the response includes the next step for the customer. Check **`next_action`** (`redirect` with `url`) in addition to `wave_launch_url` / `checkout_url`. ### When to use this Use for server-initiated mobile-money collection when you are **not** using a hosted checkout session. ### Good to know Follow the provider instructions in the response; UX is rail-specific (USSD, app redirect, etc.). ### See also [Mobile money](/build/mobile-money) · [Direct charges](/build/accept/direct-charges) · [Create checkout session](/api/checkout-sessions/CheckoutSessionsController_create) · [Transactions](/api/transactions/TransactionsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /charge/wave` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `CreateWaveChargeDto` | Field | Required | Type | Description | | ---------------- | -------- | ----------------------- | --------------------------- | | `amount` | Yes | `number` | Amount in XOF (minimum 100) | | `currency` | Yes | `string` | Must be XOF for Wave | | `organizationId` | No | `string` | - | | `merchantId` | No | `string` | - | | `customer` | Yes | `CustomerDto` | - | | `description` | No | `string` | - | | `successUrl` | No | `string` | - | | `errorUrl` | No | `string` | - | | `environment` | No | `enum ("live", "test")` | - | Example body: ```json { "amount": 1000, "currency": "XOF", "customer": { "name": "...", "phoneNumber": "..." } } ``` ## Responses | Status | Description | | ------ | ------------------------------- | | `201` | Wave charge initiated | | `400` | Invalid input or Wave API error | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/wave" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":1000,"currency":"XOF","customer":{"name":"...","phoneNumber":"..."}}' ``` ## OpenAPI * **operationId**: `ChargesController_createWaveCharge` * **Operation**: `POST /charge/wave` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get embedded card charge Source: https://docs.lomi.africa/api/charge/ChargesController_getCardCharge Get embedded card charge *** title: "Get embedded card charge" description: "Get embedded card charge" full: true method: get path: /charge/card/{id} operationId: ChargesController\_getCardCharge --------------------------------------------- ## Overview Get embedded card charge Retrieves card charge status and linked transaction when present. ### When to use this Use after client confirmation to poll status. ### See also [Create card charge](/api/charge/ChargesController_createCardCharge) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /charge/card/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------------------- | | `id` | Yes | - | Card payment id (pi\_...) | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Card charge | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/charge/card/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `ChargesController_getCardCharge` * **Operation**: `GET /charge/card/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Advanced direct charges Source: https://docs.lomi.africa/api/charge Create direct Wave, MTN, and embedded card payments when hosted checkout is not the right fit. *** title: 'Advanced direct charges' description: 'Create direct Wave, MTN, and embedded card payments when hosted checkout is not the right fit.' index: true ----------- Direct charge APIs are for teams that need lower-level control than hosted checkout or payment links. Keep using [Checkout sessions](/api/checkout-sessions) for the default hosted flow. ## When to use direct charges Use direct charges when: * Your app already owns the payment UI. * You need to start a Wave or MTN payment from a custom flow. * You need embedded card collection with client-side confirmation. * You can handle pending, failed, cancelled, and completed states yourself. ## What to build around it Direct charge integrations should include: * Server-side creation with a secret key. * Customer-visible pending and failure states. * Webhook handling for final reconciliation. * Safe handling of any `client_secret` returned for embedded card flows. ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. # Create checkout session Source: https://docs.lomi.africa/api/checkout-sessions/CheckoutSessionsController_create Create checkout session *** title: "Create checkout session" description: "Create checkout session" full: true method: post path: /checkout-sessions operationId: CheckoutSessionsController\_create ----------------------------------------------- ## Overview Create checkout session Creates a hosted checkout session so the buyer completes payment on the hosted checkout experience. Sessions expire; create a fresh session if the link lapses. ### When to use this Use for e-commerce, invoices, or any flow where you want lomi. to host payment collection and return the customer to your site. ### Good to know Prefer checkout sessions over ad-hoc charges when you need a consistent buyer experience across payment methods. For pay\_what\_you\_want products, amount must fall within the linked price minimum\_amount and maximum\_amount bounds (unit × quantity). ### See also [Payment links](/api/payment-links/PaymentLinksController_create) · [Retrieve checkout session](/api/checkout-sessions/CheckoutSessionsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /checkout-sessions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body Session payload: provide `amount` (and optional product fields) or `line_items` for a multi-product cart. Schema: `object` | Field | Required | Type | Description | | ------------------------- | -------- | ---------------------------- | -------------------------------------------------------------------------------------- | | `amount` | Yes | `number` | - | | `currency_code` | No | `enum ("XOF", "USD", "EUR")` | Optional. Falls back to organization default\_currency when omitted. | | `title` | No | `string` | - | | `description` | No | `string` | - | | `customer_id` | No | `string` | - | | `customer_email` | No | `string` | - | | `customer_name` | No | `string` | - | | `customer_phone` | No | `string` | - | | `customer_city` | No | `string` | - | | `customer_country` | No | `string` | - | | `customer_address` | No | `string` | - | | `customer_postal_code` | No | `string` | - | | `product_id` | No | `string` | - | | `price_id` | No | `string` | - | | `subscription_id` | No | `string` | - | | `allow_quantity` | No | `boolean` | - | | `quantity` | No | `number` | - | | `success_url` | No | `string` | - | | `cancel_url` | No | `string` | - | | `allow_coupon_code` | No | `boolean` | - | | `require_billing_address` | No | `boolean` | When true, show and require billing address on checkout. Default false when unset. | | `require_email` | No | `boolean` | When true, show and require customer email. Default true when unset. | | `require_phone` | No | `boolean` | When true, show and require customer phone. Default true when unset. | | `require_name` | No | `boolean` | When true, show and require customer name. Default true when unset. | | `fields` | No | `array` | Optional ordered checkout field schema. When provided, overrides require\_\* booleans. | | `payment_link_id` | No | `string` | - | | `metadata` | No | `object` | - | | `line_items` | No | `array` | - | Example body: ```json { "amount": 10000 } ``` ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/checkout-sessions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":10000}' ``` ## OpenAPI * **operationId**: `CheckoutSessionsController_create` * **Operation**: `POST /checkout-sessions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List checkout sessions Source: https://docs.lomi.africa/api/checkout-sessions/CheckoutSessionsController_findAll List checkout sessions *** title: "List checkout sessions" description: "List checkout sessions" full: true method: get path: /checkout-sessions operationId: CheckoutSessionsController\_findAll ------------------------------------------------ ## Overview List checkout sessions Lists checkout sessions with filters for status, time range, and pagination per your integration needs. ### When to use this Use for reconciliation, support tools, or exporting recent checkout attempts. ### See also [Retrieve checkout session](/api/checkout-sessions/CheckoutSessionsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /checkout-sessions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | -------- | ----- | -------- | ------ | ---------------------------------------------------------------- | | `offset` | query | No | - | Décalage pour la pagination | | `limit` | query | No | - | Nombre maximal de résultats | | `status` | query | No | - | Filtrer par statut de session (valeur checkout\_session\_status) | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/checkout-sessions?offset=1&limit=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `CheckoutSessionsController_findAll` * **Operation**: `GET /checkout-sessions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve checkout session Source: https://docs.lomi.africa/api/checkout-sessions/CheckoutSessionsController_findOne Retrieve checkout session *** title: "Retrieve checkout session" description: "Retrieve checkout session" full: true method: get path: /checkout-sessions/{id} operationId: CheckoutSessionsController\_findOne ------------------------------------------------ ## Overview Retrieve checkout session Returns session details including status and associated customer and line items where applicable. ### When to use this Poll or display after redirect from checkout, or when handling async notifications keyed by session ID. ### See also [List transactions](/api/transactions/TransactionsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /checkout-sessions/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------------ | | `id` | Yes | - | UUID de la session | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/checkout-sessions/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `CheckoutSessionsController_findOne` * **Operation**: `GET /checkout-sessions/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create discount coupon Source: https://docs.lomi.africa/api/coupons/DiscountCouponsController_create Create discount coupon *** title: "Create discount coupon" description: "Create discount coupon" full: true method: post path: /coupons operationId: DiscountCouponsController\_create ---------------------------------------------- ## Overview Create discount coupon Creates a coupon with scope and redemption rules for use at checkout or payment links. ### When to use this Use when launching promotions or segment-specific discounts. ### See also [List coupons](/api/coupons/DiscountCouponsController_findAll) · [Checkout session](/api/checkout-sessions/CheckoutSessionsController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /coupons` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/coupons" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `DiscountCouponsController_create` * **Operation**: `POST /coupons` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List discount coupons Source: https://docs.lomi.africa/api/coupons/DiscountCouponsController_findAll List discount coupons *** title: "List discount coupons" description: "List discount coupons" full: true method: get path: /coupons operationId: DiscountCouponsController\_findAll ----------------------------------------------- ## Overview List discount coupons Returns coupons configured for your organization. ### When to use this Use to populate an admin UI or audit active promotions. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /coupons` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/coupons" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `DiscountCouponsController_findAll` * **Operation**: `GET /coupons` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve discount coupon Source: https://docs.lomi.africa/api/coupons/DiscountCouponsController_findOne Retrieve discount coupon *** title: "Retrieve discount coupon" description: "Retrieve discount coupon" full: true method: get path: /coupons/{id} operationId: DiscountCouponsController\_findOne ----------------------------------------------- ## Overview Retrieve discount coupon Returns one coupon definition by ID including constraints and redemption settings. ### When to use this Use before editing copy or validating a code’s rules in your own checkout. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /coupons/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du coupon | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/coupons/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `DiscountCouponsController_findOne` * **Operation**: `GET /coupons/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Coupon performance metrics Source: https://docs.lomi.africa/api/coupons/DiscountCouponsController_getPerformance Coupon performance metrics *** title: "Coupon performance metrics" description: "Coupon performance metrics" full: true method: get path: /coupons/{id}/performance operationId: DiscountCouponsController\_getPerformance ------------------------------------------------------ ## Overview Coupon performance metrics Returns usage and performance metrics for a coupon (redemptions, revenue impact) for reporting. ### When to use this Use in marketing dashboards to measure campaign effectiveness. ### See also [Retrieve coupon](/api/coupons/DiscountCouponsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /coupons/{id}/performance` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du coupon | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Indicateurs de performance | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/coupons/value/performance" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `DiscountCouponsController_getPerformance` * **Operation**: `GET /coupons/{id}/performance` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Delete coupon Source: https://docs.lomi.africa/api/coupons/DiscountCouponsController_remove Delete coupon *** title: "Delete coupon" description: "Delete coupon" full: true method: delete path: /coupons/{id} operationId: DiscountCouponsController\_remove ---------------------------------------------- ## Overview Delete coupon Supprime le coupon. ### When to use this Use this endpoint when your flow needs `DELETE /coupons/{id}`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `DELETE /coupons/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X DELETE "https://sandbox.api.lomi.africa/coupons/ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `DiscountCouponsController_remove` * **Operation**: `DELETE /coupons/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List disputes Source: https://docs.lomi.africa/api/disputes/DisputesController_findAll List disputes *** title: "List disputes" description: "List disputes" full: true method: get path: /disputes operationId: DisputesController\_findAll ---------------------------------------- ## Overview List disputes Returns card payment disputes for your organization with optional status and date filters. ### When to use this Use for support queues, reconciliation, and automation on `DISPUTE_*` webhooks. ### See also [Get dispute](/api/disputes/DisputesController_findOne) · [Disputes guide](/build/money/disputes) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /disputes` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ----------- | ----- | -------- | ------ | ----------- | | `pageSize` | query | No | - | | | `page` | query | No | - | | | `endDate` | query | No | - | | | `startDate` | query | No | - | | | `status` | query | No | - | | ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/disputes?pageSize=1&page=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `DisputesController_findAll` * **Operation**: `GET /disputes` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get dispute Source: https://docs.lomi.africa/api/disputes/DisputesController_findOne Get dispute *** title: "Get dispute" description: "Get dispute" full: true method: get path: /disputes/{id} operationId: DisputesController\_findOne ---------------------------------------- ## Overview Get dispute Returns a single dispute by ID, including linked transaction and customer snapshot fields. ### When to use this Use after `DISPUTE_CREATED` or when drilling into a row from the disputes list. ### See also [List disputes](/api/disputes/DisputesController_findAll) · [Disputes guide](/build/money/disputes) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /disputes/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | Dispute ID | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/disputes/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `DisputesController_findOne` * **Operation**: `GET /disputes/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Submit dispute evidence Source: https://docs.lomi.africa/api/disputes/DisputesController_submitEvidence Submit dispute evidence *** title: "Submit dispute evidence" description: "Submit dispute evidence" full: true method: post path: /disputes/{id}/evidence operationId: DisputesController\_submitEvidence ----------------------------------------------- ## Overview Submit dispute evidence Attach written evidence (and optional file metadata) to a card dispute before the due date. ### When to use this Use this endpoint when your flow needs `POST /disputes/{id}/evidence` (Submit dispute evidence). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /disputes/{id}/evidence` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | Dispute ID | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ----------------- | | `200` | Evidence recorded | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/disputes/value/evidence" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `DisputesController_submitEvidence` * **Operation**: `POST /disputes/{id}/evidence` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create a customer Source: https://docs.lomi.africa/api/customers/CustomersController_create Create a customer *** title: "Create a customer" description: "Create a customer" full: true method: post path: /customers operationId: CustomersController\_create ---------------------------------------- ## Overview Create a customer Creates a customer record scoped to your organization for repeat purchases and reporting. ### When to use this Use when you have stable customer identity in your system and want card-on-file, subscriptions, or clean transaction history. ### See also [List customers](/api/customers/CustomersController_findAll) · [Update customer](/api/customers/CustomersController_update) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /customers` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | ----------------- | -------- | --------- | ----------- | | `name` | Yes | `string` | - | | `email` | No | `string` | - | | `phone_number` | No | `string` | - | | `whatsapp_number` | No | `string` | - | | `country` | No | `string` | - | | `city` | No | `string` | - | | `address` | No | `string` | - | | `postal_code` | No | `string` | - | | `is_business` | No | `boolean` | - | | `metadata` | No | `object` | - | Example body: ```json { "name": "Jane Doe" } ``` ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/customers" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Jane Doe"}' ``` ## OpenAPI * **operationId**: `CustomersController_create` * **Operation**: `POST /customers` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create customer portal session Source: https://docs.lomi.africa/api/customers/CustomersController_createPortalSession Create customer portal session *** title: "Create customer portal session" description: "Create customer portal session" full: true method: post path: /customers/{id}/portal operationId: CustomersController\_createPortalSession ----------------------------------------------------- ## Overview Create customer portal session Returns a short-lived URL so the customer can manage subscriptions and payment methods in the hosted portal. ### When to use this Use from your app when a logged-in buyer opens “Manage billing” without building portal UI yourself. ### See also [Portal audit log](/api/customers/CustomersController_getPortalAudit) · [List subscriptions](/api/subscriptions/SubscriptionsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /customers/{id}/portal` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du client | ### Query parameters *No query parameters.* ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | --------------------------- | -------- | -------------------------------------------------------------------- | ----------- | | `return_url` | No | `string` | - | | `flow_type` | No | `enum ("portal_home", "subscription_cancel", "subscription_manage")` | - | | `flow_subscription_id` | No | `string` | - | | `flow_after_completion_url` | No | `string` | - | Example body: ```json { "return_url": "string", "flow_type": "portal_home", "flow_subscription_id": "string" } ``` ## Responses | Status | Description | | ------ | -------------------------- | | `201` | Created successfully | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/customers/value/portal" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"return_url":"string","flow_type":"portal_home","flow_subscription_id":"string"}' ``` ## OpenAPI * **operationId**: `CustomersController_createPortalSession` * **Operation**: `POST /customers/{id}/portal` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List customers Source: https://docs.lomi.africa/api/customers/CustomersController_findAll List customers *** title: "List customers" description: "List customers" full: true method: get path: /customers operationId: CustomersController\_findAll ----------------------------------------- ## Overview List customers Returns a paginated customer directory with optional filters such as search text and activity. ### When to use this Use for CRM-style search, back-office lists, and exporting buyer records. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /customers` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ---------- | ----- | -------- | ------ | --------------------------------------------------------------------------------------- | | `pageSize` | query | No | - | Nombre d'éléments par page | | `page` | query | No | - | Numéro de page | | `status` | query | No | - | Filtrer par activité (active = au moins une transaction, inactive = aucune transaction) | | `type` | query | No | - | Filtrer par type de client | | `search` | query | No | - | Recherche par nom ou e-mail | ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/customers?pageSize=1&page=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `CustomersController_findAll` * **Operation**: `GET /customers` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve a customer Source: https://docs.lomi.africa/api/customers/CustomersController_findOne Retrieve a customer *** title: "Retrieve a customer" description: "Retrieve a customer" full: true method: get path: /customers/{id} operationId: CustomersController\_findOne ----------------------------------------- ## Overview Retrieve a customer Returns one customer by ID. Responds with **404** if the record is unknown or not visible to this API key. ### When to use this Use on profile pages or before updating a customer or creating a subscription. ### See also [Customer transactions](/api/customers/CustomersController_getTransactions) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /customers/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du client | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/customers/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `CustomersController_findOne` * **Operation**: `GET /customers/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Customer portal audit log Source: https://docs.lomi.africa/api/customers/CustomersController_getPortalAudit Customer portal audit log *** title: "Customer portal audit log" description: "Customer portal audit log" full: true method: get path: /customers/{id}/portal-audit operationId: CustomersController\_getPortalAudit ------------------------------------------------ ## Overview Customer portal audit log Returns portal activity for a customer (sign-ins, subscription changes, etc.) for support and compliance. ### When to use this Use when investigating billing disputes or verifying what the customer changed in the portal. ### See also [Create portal session](/api/customers/CustomersController_createPortalSession) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /customers/{id}/portal-audit` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du client | ### Query parameters | Name | In | Required | Schema | Description | | ----------- | ----- | -------- | ------ | ------------------------------------------------------ | | `eventType` | query | No | - | Filtre sur customer\_portal\_audit\_events.event\_type | | `pageSize` | query | No | - | | | `page` | query | No | - | | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/customers/value/portal-audit?eventType=value&pageSize=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `CustomersController_getPortalAudit` * **Operation**: `GET /customers/{id}/portal-audit` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List subscriptions for customer Source: https://docs.lomi.africa/api/customers/CustomersController_getSubscriptions List subscriptions for customer *** title: "List subscriptions for customer" description: "List subscriptions for customer" full: true method: get path: /customers/{id}/subscriptions operationId: CustomersController\_getSubscriptions -------------------------------------------------- ## Overview List subscriptions for customer Returns subscriptions tied to one customer ID. Responds with **404** when the customer is unknown. ### When to use this Use on customer portals showing active plans. ### See also [Retrieve customer](/api/customers/CustomersController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /customers/{id}/subscriptions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du client | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/customers/value/subscriptions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `CustomersController_getSubscriptions` * **Operation**: `GET /customers/{id}/subscriptions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List customer transactions Source: https://docs.lomi.africa/api/customers/CustomersController_getTransactions List customer transactions *** title: "List customer transactions" description: "List customer transactions" full: true method: get path: /customers/{id}/transactions operationId: CustomersController\_getTransactions ------------------------------------------------- ## Overview List customer transactions Returns transactions linked to a single customer ID for statements and dispute handling. ### When to use this Use on customer detail pages or when answering support questions tied to one buyer. ### See also [List transactions](/api/transactions/TransactionsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /customers/{id}/transactions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du client | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/customers/value/transactions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `CustomersController_getTransactions` * **Operation**: `GET /customers/{id}/transactions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Remove a customer Source: https://docs.lomi.africa/api/customers/CustomersController_remove Remove a customer *** title: "Remove a customer" description: "Remove a customer" full: true method: delete path: /customers/{id} operationId: CustomersController\_remove ---------------------------------------- ## Overview Remove a customer Stops returning the customer in list and detail views for your organization. ### When to use this Use for GDPR-style deletion requests or when you must disable a buyer record from merchant-facing APIs. ### Good to know Behavior follows platform rules for retained financial records; confirm with your compliance team for legal holds. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `DELETE /customers/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du client | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X DELETE "https://sandbox.api.lomi.africa/customers/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `CustomersController_remove` * **Operation**: `DELETE /customers/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update a customer Source: https://docs.lomi.africa/api/customers/CustomersController_update Update a customer *** title: "Update a customer" description: "Update a customer" full: true method: patch path: /customers/{id} operationId: CustomersController\_update ---------------------------------------- ## Overview Update a customer Partial update; send only fields that change (email, phone, metadata, etc.). ### When to use this Use when buyers edit their profile or when syncing CRM changes into lomi. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /customers/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | UUID du client | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | ----------------- | -------- | --------- | ----------- | | `name` | No | `string` | - | | `email` | No | `string` | - | | `phone_number` | No | `string` | - | | `whatsapp_number` | No | `string` | - | | `country` | No | `string` | - | | `city` | No | `string` | - | | `address` | No | `string` | - | | `postal_code` | No | `string` | - | | `is_business` | No | `boolean` | - | | `metadata` | No | `object` | - | Example body: ```json { "name": "string", "email": "string", "phone_number": "string" } ``` ## Responses | Status | Description | | ------ | ------------------------------------------ | | `200` | Success | | `400` | Bad request, invalid or missing parameters | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/customers/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"string","email":"string","phone_number":"string"}' ``` ## OpenAPI * **operationId**: `CustomersController_update` * **Operation**: `PATCH /customers/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Receivables aging buckets Source: https://docs.lomi.africa/api/finance/FinanceController_aging Receivables aging buckets *** title: "Receivables aging buckets" description: "Receivables aging buckets" full: true method: get path: /finance/aging operationId: FinanceController\_aging ------------------------------------- ## Overview Receivables aging buckets ### When to use this Use this endpoint when your flow needs `GET /finance/aging` (Receivables aging buckets). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /finance/aging` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/finance/aging" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `FinanceController_aging` * **Operation**: `GET /finance/aging` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Daily cash in and out over a date range Source: https://docs.lomi.africa/api/finance/FinanceController_cashflow Daily cash in and out over a date range *** title: "Daily cash in and out over a date range" description: "Daily cash in and out over a date range" full: true method: get path: /finance/cashflow operationId: FinanceController\_cashflow ---------------------------------------- ## Overview Daily cash in and out over a date range ### When to use this Use this endpoint when your flow needs `GET /finance/cashflow` (Daily cash in and out over a date range). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /finance/cashflow` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------ | ----- | -------- | ------ | ----------- | | `end_date` | query | No | - | | | `start_date` | query | No | - | | ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/finance/cashflow?end_date=value&start_date=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `FinanceController_cashflow` * **Operation**: `GET /finance/cashflow` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Reconcile settlements vs transactions vs payouts Source: https://docs.lomi.africa/api/finance/FinanceController_reconcile Reconcile settlements vs transactions vs payouts *** title: "Reconcile settlements vs transactions vs payouts" description: "Reconcile settlements vs transactions vs payouts" full: true method: get path: /finance/reconcile operationId: FinanceController\_reconcile ----------------------------------------- ## Overview Reconcile settlements vs transactions vs payouts ### When to use this Use this endpoint when your flow needs `GET /finance/reconcile` (Reconcile settlements vs transactions vs payouts). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /finance/reconcile` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/finance/reconcile" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `FinanceController_reconcile` * **Operation**: `GET /finance/reconcile` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Finance summary Source: https://docs.lomi.africa/api/finance/FinanceController_summary Finance summary *** title: "Finance summary" description: "Finance summary" full: true method: get path: /finance/summary operationId: FinanceController\_summary --------------------------------------- ## Overview Finance summary Cash position, receivables outstanding, overdue invoices, upcoming payouts, refund rate, and dispute exposure. ### When to use this Use this endpoint when your flow needs `GET /finance/summary` (Finance summary). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /finance/summary` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/finance/summary" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `FinanceController_summary` * **Operation**: `GET /finance/summary` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create an export job Source: https://docs.lomi.africa/api/exports/MerchantExportsController_create Create an export job *** title: "Create an export job" description: "Create an export job" full: true method: post path: /exports operationId: MerchantExportsController\_create ---------------------------------------------- ## Overview Create an export job Start a CSV or PDF export (transactions, customers, monthly statement, or accounting journal). Poll GET /exports/:id for download\_url. ### When to use this Use this endpoint when your flow needs `POST /exports` (Create an export job). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /exports` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/exports" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `MerchantExportsController_create` * **Operation**: `POST /exports` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List exports Source: https://docs.lomi.africa/api/exports/MerchantExportsController_findAll List exports *** title: "List exports" description: "List exports" full: true method: get path: /exports operationId: MerchantExportsController\_findAll ----------------------------------------------- ## Overview List exports Jobs d’export récents pour l’organisation. ### When to use this Use this endpoint when your flow needs `GET /exports`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /exports` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/exports" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MerchantExportsController_findAll` * **Operation**: `GET /exports` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get export job status and download URL Source: https://docs.lomi.africa/api/exports/MerchantExportsController_findOne Get export job status and download URL *** title: "Get export job status and download URL" description: "Get export job status and download URL" full: true method: get path: /exports/{id} operationId: MerchantExportsController\_findOne ----------------------------------------------- ## Overview Get export job status and download URL ### When to use this Use this endpoint when your flow needs `GET /exports/{id}` (Get export job status and download URL). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /exports/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/exports/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MerchantExportsController_findOne` * **Operation**: `GET /exports/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List logs Source: https://docs.lomi.africa/api/logs/LogsController_findAll List logs *** title: "List logs" description: "List logs" full: true method: get path: /logs operationId: LogsController\_findAll ------------------------------------ ## Overview List logs Returns paginated logs for the organization. The `type` query parameter selects which log stream to read: `api_request`, `api_error`, `webhook_delivery`, or `activity`. ### When to use this Use when debugging API errors, auditing webhook deliveries, or building support dashboards. ### See also [Retrieve log entry](/api/logs/LogsController_findOne) · [Webhook delivery logs](/api/webhooks/WebhookDeliveryLogsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /logs` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------ | ----- | -------- | ------ | ------------------------------------------------------------------------------ | | `event` | query | No | - | Filter activity logs by event type | | `failed` | query | No | - | Only failed webhook deliveries | | `success` | query | No | - | Only successful webhook deliveries | | `webhook_id` | query | No | - | Filter webhook\_delivery logs by webhook ID | | `severity` | query | No | - | | | `status` | query | No | - | Comma-separated HTTP status codes (api\_request, api\_error). Example: 400,500 | | `end_date` | query | No | - | ISO 8601 end timestamp (inclusive) | | `start_date` | query | No | - | ISO 8601 start timestamp (inclusive) | | `offset` | query | No | - | | | `limit` | query | No | - | | | `type` | query | Yes | - | Log stream to query | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Paginated log list | | `400` | Invalid query parameters | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/logs?event=value&failed=true" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `LogsController_findAll` * **Operation**: `GET /logs` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve log entry Source: https://docs.lomi.africa/api/logs/LogsController_findOne Retrieve log entry *** title: "Retrieve log entry" description: "Retrieve log entry" full: true method: get path: /logs/{id} operationId: LogsController\_findOne ------------------------------------ ## Overview Retrieve log entry Returns a single log entry by ID. Pass `type` to select the log stream. Responds with **404** when the entry does not exist or is outside the API key organization scope. ### When to use this Use when drilling into one failed request, webhook delivery, or activity event from a list view. ### See also [List logs](/api/logs/LogsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /logs/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------- | | `id` | Yes | - | Log entry UUID | ### Query parameters | Name | In | Required | Schema | Description | | ------ | ----- | -------- | ------ | ------------------- | | `type` | query | Yes | - | Log stream to query | ## Responses | Status | Description | | ------ | ------------------------------ | | `200` | Log entry details | | `401` | Invalid or missing API key | | `404` | Log not found or access denied | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/logs/value?type=api_request" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `LogsController_findOne` * **Operation**: `GET /logs/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Créer une facture Source: https://docs.lomi.africa/api/invoices/InvoicesController_create Créer une facture *** title: "Créer une facture" description: "Créer une facture" full: true method: post path: /invoices operationId: InvoicesController\_create --------------------------------------- ## Overview Créer une facture ### When to use this Use this endpoint when your flow needs `POST /invoices` (Créer une facture). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /invoices` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/invoices" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `InvoicesController_create` * **Operation**: `POST /invoices` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Créer ou récupérer une session de paiement de facture Source: https://docs.lomi.africa/api/invoices/InvoicesController_createCheckoutSession Créer ou récupérer une session de paiement de facture *** title: "Créer ou récupérer une session de paiement de facture" description: "Créer ou récupérer une session de paiement de facture" full: true method: post path: /invoices/{id}/checkout-session operationId: InvoicesController\_createCheckoutSession ------------------------------------------------------ ## Overview Créer ou récupérer une session de paiement de facture ### When to use this Use this endpoint when your flow needs `POST /invoices/{id}/checkout-session` (Créer ou récupérer une session de paiement de facture). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /invoices/{id}/checkout-session` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/invoices/value/checkout-session" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `InvoicesController_createCheckoutSession` * **Operation**: `POST /invoices/{id}/checkout-session` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Finalize a draft invoice Source: https://docs.lomi.africa/api/invoices/InvoicesController_finalize Finalize a draft invoice *** title: "Finalize a draft invoice" description: "Finalize a draft invoice" full: true method: post path: /invoices/{id}/finalize operationId: InvoicesController\_finalize ----------------------------------------- ## Overview Finalize a draft invoice Marks the invoice as sent and creates a hosted checkout session. Does not email the customer. ### When to use this Use this endpoint when your flow needs `POST /invoices/{id}/finalize` (Finalize a draft invoice). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /invoices/{id}/finalize` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/invoices/value/finalize" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `InvoicesController_finalize` * **Operation**: `POST /invoices/{id}/finalize` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Lister les factures Source: https://docs.lomi.africa/api/invoices/InvoicesController_findAll Lister les factures *** title: "Lister les factures" description: "Lister les factures" full: true method: get path: /invoices operationId: InvoicesController\_findAll ---------------------------------------- ## Overview Lister les factures ### When to use this Use this endpoint when your flow needs `GET /invoices` (Lister les factures). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /invoices` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------ | ----- | -------- | ------ | ----------- | | `limit` | query | No | - | | | `cursor` | query | No | - | | | `search` | query | No | - | | | `customerId` | query | No | - | | | `status` | query | No | - | | ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/invoices?limit=1&cursor=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `InvoicesController_findAll` * **Operation**: `GET /invoices` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Obtenir une facture Source: https://docs.lomi.africa/api/invoices/InvoicesController_findOne Obtenir une facture *** title: "Obtenir une facture" description: "Obtenir une facture" full: true method: get path: /invoices/{id} operationId: InvoicesController\_findOne ---------------------------------------- ## Overview Obtenir une facture ### When to use this Use this endpoint when your flow needs `GET /invoices/{id}` (Obtenir une facture). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /invoices/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/invoices/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `InvoicesController_findOne` * **Operation**: `GET /invoices/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Invoice PDF Source: https://docs.lomi.africa/api/invoices/InvoicesController_pdf Invoice PDF *** title: "Invoice PDF" description: "Invoice PDF" full: true method: get path: /invoices/{id}/pdf operationId: InvoicesController\_pdf ------------------------------------ ## Overview Invoice PDF Returns a hosted invoice URL and a download\_url for the PDF. Send download\_url to the human, or save the file locally from stdio MCP / the CLI. ### When to use this Use this endpoint when your flow needs `GET /invoices/{id}/pdf` (Invoice PDF). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /invoices/{id}/pdf` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/invoices/value/pdf" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `InvoicesController_pdf` * **Operation**: `GET /invoices/{id}/pdf` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Send an invoice reminder Source: https://docs.lomi.africa/api/invoices/InvoicesController_remind Send an invoice reminder *** title: "Send an invoice reminder" description: "Send an invoice reminder" full: true method: post path: /invoices/{id}/remind operationId: InvoicesController\_remind --------------------------------------- ## Overview Send an invoice reminder Queue a reminder email for a sent or overdue invoice. ### When to use this Use this endpoint when your flow needs `POST /invoices/{id}/remind` (Send an invoice reminder). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /invoices/{id}/remind` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/invoices/value/remind" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `InvoicesController_remind` * **Operation**: `POST /invoices/{id}/remind` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Send an invoice Source: https://docs.lomi.africa/api/invoices/InvoicesController_send Send an invoice *** title: "Send an invoice" description: "Send an invoice" full: true method: post path: /invoices/{id}/send operationId: InvoicesController\_send ------------------------------------- ## Overview Send an invoice Finalize the invoice, create a hosted pay link, and queue the invoice email when a customer email exists. ### When to use this Use this endpoint when your flow needs `POST /invoices/{id}/send` (Send an invoice). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /invoices/{id}/send` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/invoices/value/send" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `InvoicesController_send` * **Operation**: `POST /invoices/{id}/send` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Modifier une facture Source: https://docs.lomi.africa/api/invoices/InvoicesController_update Modifier une facture *** title: "Modifier une facture" description: "Modifier une facture" full: true method: patch path: /invoices/{id} operationId: InvoicesController\_update --------------------------------------- ## Overview Modifier une facture ### When to use this Use this endpoint when your flow needs `PATCH /invoices/{id}` (Modifier une facture). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /invoices/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/invoices/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `InvoicesController_update` * **Operation**: `PATCH /invoices/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Void an invoice Source: https://docs.lomi.africa/api/invoices/InvoicesController_voidInvoice Void an invoice *** title: "Void an invoice" description: "Void an invoice" full: true method: post path: /invoices/{id}/void operationId: InvoicesController\_voidInvoice -------------------------------------------- ## Overview Void an invoice Cancel a draft, sent, or overdue invoice. ### When to use this Use this endpoint when your flow needs `POST /invoices/{id}/void` (Void an invoice). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /invoices/{id}/void` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/invoices/value/void" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `InvoicesController_voidInvoice` * **Operation**: `POST /invoices/{id}/void` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get merchant ARR Source: https://docs.lomi.africa/api/merchants/MerchantsController_getArr Get merchant ARR *** title: "Get merchant ARR" description: "Get merchant ARR" full: true method: get path: /merchants/{id}/arr operationId: MerchantsController\_getArr ---------------------------------------- ## Overview Get merchant ARR Returns annualized recurring revenue for the merchant tied to the given ID. ### When to use this Use for annual planning views when you track merchants individually. ### See also [Merchant MRR](/api/merchants/MerchantsController_getMrr) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /merchants/{id}/arr` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------- | | `id` | Yes | - | Merchant UUID | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/merchants/value/arr" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MerchantsController_getArr` * **Operation**: `GET /merchants/{id}/arr` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get merchant balance Source: https://docs.lomi.africa/api/merchants/MerchantsController_getBalance Get merchant balance *** title: "Get merchant balance" description: "Get merchant balance" full: true method: get path: /merchants/{id}/balance operationId: MerchantsController\_getBalance -------------------------------------------- ## Overview Get merchant balance Returns account balance for a merchant in the requested currency. ### When to use this Use when a merchant ID is the scope key for wallet or treasury displays. ### Good to know Requires `currency_code` (XOF, USD, or EUR). ### See also [Account balances](/api/balances/AccountsController_getBalance) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /merchants/{id}/balance` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------- | | `id` | Yes | - | Merchant UUID | ### Query parameters | Name | In | Required | Schema | Description | | --------------- | ----- | -------- | ------ | ----------- | | `currency_code` | query | Yes | - | | ## Responses | Status | Description | | ------ | ---------------------- | | `200` | Success | | `400` | Missing currency\_code | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/merchants/value/balance?currency_code=XOF" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MerchantsController_getBalance` * **Operation**: `GET /merchants/{id}/balance` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get merchant details Source: https://docs.lomi.africa/api/merchants/MerchantsController_getDetails Get merchant details *** title: "Get merchant details" description: "Get merchant details" full: true method: get path: /merchants/{id} operationId: MerchantsController\_getDetails -------------------------------------------- ## Overview Get merchant details Returns merchant profile data and organization-level revenue metrics for the given merchant ID. ### When to use this Use when your integration still references a merchant ID or you need legacy merchant-scoped reads. ### See also [Organizations](/build/platform/organizations) · [Merchant ARR](/api/merchants/MerchantsController_getArr) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /merchants/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------- | | `id` | Yes | - | Merchant UUID | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------- | | `200` | Success | | `403` | Merchant ID mismatch | | `404` | Merchant not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/merchants/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MerchantsController_getDetails` * **Operation**: `GET /merchants/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get merchant MRR Source: https://docs.lomi.africa/api/merchants/MerchantsController_getMrr Get merchant MRR *** title: "Get merchant MRR" description: "Get merchant MRR" full: true method: get path: /merchants/{id}/mrr operationId: MerchantsController\_getMrr ---------------------------------------- ## Overview Get merchant MRR Returns monthly recurring revenue for the merchant tied to the given ID. ### When to use this Use for subscription analytics when operating on a merchant-scoped identifier. ### See also [Organization metrics](/api/organizations/OrganizationsController_getMetrics) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /merchants/{id}/mrr` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------- | | `id` | Yes | - | Merchant UUID | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/merchants/value/mrr" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MerchantsController_getMrr` * **Operation**: `GET /merchants/{id}/mrr` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create account session Source: https://docs.lomi.africa/api/network/NetworkAccountsController_createAccountSession Create account session *** title: "Create account session" description: "Create account session" full: true method: post path: /network/account-sessions operationId: NetworkAccountsController\_createAccountSession ------------------------------------------------------------ ## Overview Create account session Mints a short-lived `client_secret` (`nas_...`) that your front end passes to the embedded member components: `payments`, `payouts`, `balance`, `onboarding`, and `notification_banner`. Sessions expire after 60 minutes and are scoped to one Member Account. ### When to use this Use to render member surfaces inside your own pages instead of sending members to the lomi. dashboard. Create a new session on each page load. ### Good to know Operator secret key **without** `Lomi-Account`. Requires `account.read` for the key environment and `member_dashboard` set to `full` or `member_mode` on your operator profile. Never expose your Operator key to the browser; only the `client_secret` goes client-side. ### See also [Create login link](/api/network/NetworkAccountsController_createLoginLink) · [lomi. Network guide](/build/platform/network#embedded-components) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /network/account-sessions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body JSON request payload. Schema: `CreateAccountSessionDto` | Field | Required | Type | Description | | ------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------ | | `account` | Yes | `string` | Member Account the embedded components are rendered for. Must be an active membership of your Network. | | `components` | No | `object` | Components the session may render. Omitted components default to enabled. | Example body: ```json { "account": "acct_1a2b3c4d5e6f7g8h" } ``` ## Responses | Status | Description | | ------ | --------------------------------------------------------------------------------------------------------------------------- | | `201` | Account session created (object: account\_session): \{ account, client\_secret, expires\_at, components, embed\_base\_url } | | `400` | Membership inactive, member dashboard disabled, or Lomi-Account header sent | | `404` | Member Account not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/network/account-sessions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"account":"acct_1a2b3c4d5e6f7g8h"}' ``` ## OpenAPI * **operationId**: `NetworkAccountsController_createAccountSession` * **Operation**: `POST /network/account-sessions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create login link Source: https://docs.lomi.africa/api/network/NetworkAccountsController_createLoginLink Create login link *** title: "Create login link" description: "Create login link" full: true method: post path: /network/accounts/{account}/login\_links operationId: NetworkAccountsController\_createLoginLink ------------------------------------------------------- ## Overview Create login link Mints a single-use URL that signs the owner of a Member Account (`acct_...`) into their lomi. dashboard in member mode. The link expires after 5 minutes. ### When to use this Use when a member clicks "Open lomi. dashboard" inside your product and you want to drop them on their balance, payouts, or open requirements without a separate login. ### Good to know Operator secret key **without** `Lomi-Account`. Requires the `account.login_link` capability for the key environment and an active membership. Create the link server-side at click time and redirect; never email, log, or embed it in public pages. ### See also [Create account session](/api/network/NetworkAccountsController_createAccountSession) · [lomi. Network guide](/build/platform/network#member-dashboard-and-login-links) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /network/accounts/{account}/login_links` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | --------- | -------- | ------ | ----------------------------- | | `account` | Yes | - | Member Account id (acct\_...) | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ------------------------------------------------------------------------------------- | | `201` | Login link created (object: login\_link): \{ account, url, created\_at, expires\_at } | | `400` | Capability missing, membership inactive, or Lomi-Account header sent | | `404` | Member Account not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/network/accounts/value/login_links" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `NetworkAccountsController_createLoginLink` * **Operation**: `POST /network/accounts/{account}/login_links` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create a meter Source: https://docs.lomi.africa/api/meters/MetersController_create Create a meter *** title: "Create a meter" description: "Create a meter" full: true method: post path: /meters operationId: MetersController\_create ------------------------------------- ## Overview Create a meter Defines a billable metric for usage-based products. Events with a matching `code` update meter balances when processed. ### When to use this First step in usage billing: create a meter before ingesting usage events or enrolling customers on usage-based products. ### See also [Usage billing guide](/build/billing/usage-billing) · [Record usage event](/api/usage/UsageEventsController_ingest) · [List meters](/api/meters/MetersController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /meters` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/meters" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `MetersController_create` * **Operation**: `POST /meters` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List meters Source: https://docs.lomi.africa/api/meters/MetersController_findAll List meters *** title: "List meters" description: "List meters" full: true method: get path: /meters operationId: MetersController\_findAll -------------------------------------- ## Overview List meters Returns meters for your organization, optionally filtered by product or active status. ### When to use this Use to display configured billable metrics or pick a `meter_id` for balance reads. ### See also [Create meter](/api/meters/MetersController_create) · [Get meter](/api/meters/MetersController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /meters` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ----------- | ----- | -------- | ------ | ----------- | | `isActive` | query | No | - | | | `productId` | query | No | - | | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/meters?isActive=true&productId=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MetersController_findAll` * **Operation**: `GET /meters` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get a meter Source: https://docs.lomi.africa/api/meters/MetersController_findOne Get a meter *** title: "Get a meter" description: "Get a meter" full: true method: get path: /meters/{id} operationId: MetersController\_findOne -------------------------------------- ## Overview Get a meter Returns one meter by ID, including filter and aggregation configuration. ### When to use this Use when you store a meter ID and need the latest filter/aggregation rules. ### See also [List meters](/api/meters/MetersController_findAll) · [Meter balance](/api/meters/MetersController_getBalance) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /meters/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | Meter ID | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/meters/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MetersController_findOne` * **Operation**: `GET /meters/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get meter balance for a customer Source: https://docs.lomi.africa/api/meters/MetersController_getBalance Get meter balance for a customer *** title: "Get meter balance for a customer" description: "Get meter balance for a customer" full: true method: get path: /meters/{id}/balances/{customerId} operationId: MetersController\_getBalance ----------------------------------------- ## Overview Get meter balance for a customer Returns consumed, credited, and net balance units for a customer on a specific meter. ### When to use this Use for prepaid wallets, usage dashboards, or entitlement checks before granting access. ### See also [Credit wallet](/api/usage/UsageBillingController_creditWallet) · [Record usage event](/api/usage/UsageEventsController_ingest) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /meters/{id}/balances/{customerId}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/meters/id_value/balances/customerId_value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `MetersController_getBalance` * **Operation**: `GET /meters/{id}/balances/{customerId}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update a meter Source: https://docs.lomi.africa/api/meters/MetersController_update Update a meter *** title: "Update a meter" description: "Update a meter" full: true method: patch path: /meters/{id} operationId: MetersController\_update ------------------------------------- ## Overview Update a meter Updates filter, aggregation, or active status on an existing meter. ### When to use this Use when billing rules change; deactivate meters instead of deleting when historical usage must remain. ### See also [Get meter](/api/meters/MetersController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /meters/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | Meter ID | ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/meters/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `MetersController_update` * **Operation**: `PATCH /meters/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create organization Source: https://docs.lomi.africa/api/organizations/OrganizationsController_create Create organization *** title: "Create organization" description: "Create organization" full: true method: post path: /organizations operationId: OrganizationsController\_create -------------------------------------------- ## Overview Create organization Ouvre un nouvel espace et renvoie une clé secrète une fois. ### When to use this Use this endpoint when your flow needs `POST /organizations`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /organizations` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/organizations" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `OrganizationsController_create` * **Operation**: `POST /organizations` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Mint organization secret Source: https://docs.lomi.africa/api/organizations/OrganizationsController_createKey Mint organization secret *** title: "Mint organization secret" description: "Mint organization secret" full: true method: post path: /organizations/{id}/keys operationId: OrganizationsController\_createKey ----------------------------------------------- ## Overview Mint organization secret Crée une clé secrète pour un espace auquel le marchand appartient déjà. ### When to use this Use this endpoint when your flow needs `POST /organizations/{id}/keys`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /organizations/{id}/keys` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/organizations/ID/keys" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `OrganizationsController_createKey` * **Operation**: `POST /organizations/{id}/keys` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List organizations Source: https://docs.lomi.africa/api/organizations/OrganizationsController_findAll List organizations *** title: "List organizations" description: "List organizations" full: true method: get path: /organizations operationId: OrganizationsController\_findAll --------------------------------------------- ## Overview List organizations Returns organizations visible to the authenticated API key (typically your active organization). ### When to use this Use to read organization profile fields, pricing mode, and settings scoped to your integration key. ### See also [Organization metrics](/api/organizations/OrganizationsController_getMetrics) · [Organizations guide](/build/platform/organizations) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /organizations` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/organizations" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `OrganizationsController_findAll` * **Operation**: `GET /organizations` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve organization Source: https://docs.lomi.africa/api/organizations/OrganizationsController_findOne Retrieve organization *** title: "Retrieve organization" description: "Retrieve organization" full: true method: get path: /organizations/{id} operationId: OrganizationsController\_findOne --------------------------------------------- ## Overview Retrieve organization Returns one organization by ID. The ID must match the organization tied to your API key. ### When to use this Use when you already store an organization ID and need a fresh profile snapshot. ### See also [List organizations](/api/organizations/OrganizationsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /organizations/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ------------------ | | `200` | L'organisation | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/organizations/id_value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `OrganizationsController_findOne` * **Operation**: `GET /organizations/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Organization metrics Source: https://docs.lomi.africa/api/organizations/OrganizationsController_getMetrics Organization metrics *** title: "Organization metrics" description: "Organization metrics" full: true method: get path: /organizations/metrics operationId: OrganizationsController\_getMetrics ------------------------------------------------ ## Overview Organization metrics Returns MRR, ARR, LTV, revenue, and customer counts for your organization. ### When to use this Use for partner dashboards, investor reporting, or internal growth analytics. ### See also [Organizations guide](/build/platform/organizations) · [Merchant MRR](/api/merchants/MerchantsController_getMrr) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /organizations/metrics` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------------------------- | | `200` | Indicateurs de l'organisation | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/organizations/metrics" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `OrganizationsController_getMetrics` * **Operation**: `GET /organizations/metrics` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get Radar settings Source: https://docs.lomi.africa/api/organizations/OrganizationsController_getRadarSettings Get Radar settings *** title: "Get Radar settings" description: "Get Radar settings" full: true method: get path: /organizations/radar-settings operationId: OrganizationsController\_getRadarSettings ------------------------------------------------------ ## Overview Get Radar settings Returns whether lomi. Radar screening is enabled for the organization, the monitor/block mode, and card-network passthrough preferences. ### When to use this Use before toggling Radar in your own settings UI or to confirm org configuration in support tools. ### See also [Update Radar settings](/api/organizations/OrganizationsController_updateRadarSettings) · [lomi. Radar guide](/build/money/radar) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /organizations/radar-settings` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/organizations/radar-settings" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `OrganizationsController_getRadarSettings` * **Operation**: `GET /organizations/radar-settings` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update Radar settings Source: https://docs.lomi.africa/api/organizations/OrganizationsController_updateRadarSettings Update Radar settings *** title: "Update Radar settings" description: "Update Radar settings" full: true method: patch path: /organizations/radar-settings operationId: OrganizationsController\_updateRadarSettings --------------------------------------------------------- ## Overview Update Radar settings Enables or disables Radar screening and updates monitor/block mode or card-network passthrough for the organization. ### When to use this Use when onboarding merchants to fraud screening or changing how risky charges are handled. ### Good to know Radar is opt-in. When `mode` is `block`, charges that hit block rules are rejected before completion. ### See also [Get Radar settings](/api/organizations/OrganizationsController_getRadarSettings) · [List risk assessments](/api/risk-assessments/RadarController_listAssessments) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /organizations/radar-settings` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `UpdateRadarSettingsDto` | Field | Required | Type | Description | | -------------------------- | -------- | --------------------------- | ----------- | | `enabled` | No | `boolean` | - | | `mode` | No | `enum ("monitor", "block")` | - | | `stripe_radar_passthrough` | No | `boolean` | - | Example body: ```json { "enabled": true, "mode": "monitor", "stripe_radar_passthrough": true } ``` ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/organizations/radar-settings" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled":true,"mode":"monitor","stripe_radar_passthrough":true}' ``` ## OpenAPI * **operationId**: `OrganizationsController_updateRadarSettings` * **Operation**: `PATCH /organizations/radar-settings` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Archive payment link Source: https://docs.lomi.africa/api/payment-links/PaymentLinksController_archive Archive payment link *** title: "Archive payment link" description: "Archive payment link" full: true method: delete path: /payment-links/{id} operationId: PaymentLinksController\_archive -------------------------------------------- ## Overview Archive payment link Désactive le lien. ### When to use this Use this endpoint when your flow needs `DELETE /payment-links/{id}`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `DELETE /payment-links/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X DELETE "https://sandbox.api.lomi.africa/payment-links/ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `PaymentLinksController_archive` * **Operation**: `DELETE /payment-links/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create payment link Source: https://docs.lomi.africa/api/payment-links/PaymentLinksController_create Create payment link *** title: "Create payment link" description: "Create payment link" full: true method: post path: /payment-links operationId: PaymentLinksController\_create ------------------------------------------- ## Overview Create payment link Creates a shareable link: product-backed links pull catalog amounts; instant links collect a fixed amount you specify. ### When to use this Use for invoices, social selling, or lightweight payment pages without building full checkout. ### See also [List payment links](/api/payment-links/PaymentLinksController_findAll) · [Checkout sessions](/api/checkout-sessions/CheckoutSessionsController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /payment-links` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | ------------------------- | -------- | ----------------------------- | -------------------------------------------------------------------------------------- | | `link_type` | Yes | `enum ("product", "instant")` | - | | `title` | Yes | `string` | - | | `currency_code` | Yes | `enum ("XOF", "USD", "EUR")` | - | | `description` | No | `string` | - | | `amount` | No | `number` | - | | `product_id` | No | `string` | - | | `price_id` | No | `string` | - | | `allow_coupon_code` | No | `boolean` | - | | `allow_quantity` | No | `boolean` | - | | `require_billing_address` | No | `boolean` | When true, show and require billing address on checkout. Default false when unset. | | `require_email` | No | `boolean` | When true, show and require customer email. Default true when unset. | | `require_phone` | No | `boolean` | When true, show and require customer phone. Default true when unset. | | `require_name` | No | `boolean` | When true, show and require customer name. Default true when unset. | | `fields` | No | `array` | Optional ordered checkout field schema. When provided, overrides require\_\* booleans. | | `expires_at` | No | `string` | - | | `success_url` | No | `string` | - | | `cancel_url` | No | `string` | - | | `metadata` | No | `object` | - | Example body: ```json { "link_type": "product", "title": "string", "currency_code": "XOF" } ``` ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/payment-links" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"link_type":"product","title":"string","currency_code":"XOF"}' ``` ## OpenAPI * **operationId**: `PaymentLinksController_create` * **Operation**: `POST /payment-links` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List payment links Source: https://docs.lomi.africa/api/payment-links/PaymentLinksController_findAll List payment links *** title: "List payment links" description: "List payment links" full: true method: get path: /payment-links operationId: PaymentLinksController\_findAll -------------------------------------------- ## Overview List payment links Returns payment links with optional filters for state and purpose. ### When to use this Use to audit which links are still active and their target amounts or products. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /payment-links` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ---------- | ----- | -------- | ------ | --------------------------- | | `offset` | query | No | - | Décalage pour la pagination | | `limit` | query | No | - | Nombre maximal de résultats | | `isActive` | query | No | - | Filtrer par statut actif | | `linkType` | query | No | - | Filtrer par type de lien | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/payment-links?offset=1&limit=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `PaymentLinksController_findAll` * **Operation**: `GET /payment-links` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve payment link Source: https://docs.lomi.africa/api/payment-links/PaymentLinksController_findOne Retrieve payment link *** title: "Retrieve payment link" description: "Retrieve payment link" full: true method: get path: /payment-links/{id} operationId: PaymentLinksController\_findOne -------------------------------------------- ## Overview Retrieve payment link Returns URLs, visibility, and status for a single link. ### When to use this Use before resharing a link or embedding it in messaging. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /payment-links/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------------------ | | `id` | Yes | - | UUID du lien de paiement | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/payment-links/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `PaymentLinksController_findOne` * **Operation**: `GET /payment-links/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update payment link Source: https://docs.lomi.africa/api/payment-links/PaymentLinksController_update Update payment link *** title: "Update payment link" description: "Update payment link" full: true method: patch path: /payment-links/{id} operationId: PaymentLinksController\_update ------------------------------------------- ## Overview Update payment link Titre, URLs, champs checkout, expiration, ou montant. ### When to use this Use this endpoint when your flow needs `PATCH /payment-links/{id}`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /payment-links/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/payment-links/ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `PaymentLinksController_update` * **Operation**: `PATCH /payment-links/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create payment request Source: https://docs.lomi.africa/api/payment-requests/PaymentRequestsController_create Create payment request *** title: "Create payment request" description: "Create payment request" full: true method: post path: /payment-requests operationId: PaymentRequestsController\_create ---------------------------------------------- ## Overview Create payment request Creates a payer-facing request with amount, expiry, and metadata for reconciliation. ### When to use this Use for “pay this invoice” or POS-style requests where the payer confirms on their device. ### See also [Retrieve payment request](/api/payment-requests/PaymentRequestsController_findOne) · [Transactions](/api/transactions/TransactionsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /payment-requests` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | ------------------- | -------- | ---------------------------- | ----------- | | `amount` | Yes | `number` | - | | `currency_code` | Yes | `enum ("XOF", "USD", "EUR")` | - | | `description` | No | `string` | - | | `customer_id` | No | `string` | - | | `expiry_date` | Yes | `string` | - | | `payment_reference` | No | `string` | - | | `metadata` | No | `object` | - | Example body: ```json { "amount": 0, "currency_code": "XOF", "expiry_date": "string" } ``` ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/payment-requests" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":0,"currency_code":"XOF","expiry_date":"string"}' ``` ## OpenAPI * **operationId**: `PaymentRequestsController_create` * **Operation**: `POST /payment-requests` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List payment requests Source: https://docs.lomi.africa/api/payment-requests/PaymentRequestsController_findAll List payment requests *** title: "List payment requests" description: "List payment requests" full: true method: get path: /payment-requests operationId: PaymentRequestsController\_findAll ----------------------------------------------- ## Overview List payment requests Returns a paginated ledger of requests with optional filters for status or references. ### When to use this Use for finance teams tracking outstanding requests. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /payment-requests` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------ | ----- | -------- | ------ | --------------------------- | | `offset` | query | No | - | Décalage pour la pagination | | `limit` | query | No | - | Nombre maximal de résultats | | `customerId` | query | No | - | Filtrer par ID client | | `status` | query | No | - | Filtrer par statut | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/payment-requests?offset=1&limit=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `PaymentRequestsController_findAll` * **Operation**: `GET /payment-requests` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve payment request Source: https://docs.lomi.africa/api/payment-requests/PaymentRequestsController_findOne Retrieve payment request *** title: "Retrieve payment request" description: "Retrieve payment request" full: true method: get path: /payment-requests/{id} operationId: PaymentRequestsController\_findOne ----------------------------------------------- ## Overview Retrieve payment request Returns the latest state, amounts, and payer reference data for one request. ### When to use this Use on status pages and after callbacks keyed by request ID. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /payment-requests/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------------ | | `id` | Yes | - | UUID de la demande | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/payment-requests/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `PaymentRequestsController_findOne` * **Operation**: `GET /payment-requests/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Add a payout method Source: https://docs.lomi.africa/api/payout-methods/PayoutMethodsController_create Add a payout method *** title: "Add a payout method" description: "Add a payout method" full: true method: post path: /payout-methods operationId: PayoutMethodsController\_create -------------------------------------------- ## Overview Add a payout method ### When to use this Use this endpoint when your flow needs `POST /payout-methods` (Add a payout method). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /payout-methods` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/payout-methods" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `PayoutMethodsController_create` * **Operation**: `POST /payout-methods` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List payout methods Source: https://docs.lomi.africa/api/payout-methods/PayoutMethodsController_list List payout methods *** title: "List payout methods" description: "List payout methods" full: true method: get path: /payout-methods operationId: PayoutMethodsController\_list ------------------------------------------ ## Overview List payout methods ### When to use this Use this endpoint when your flow needs `GET /payout-methods` (List payout methods). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /payout-methods` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/payout-methods" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `PayoutMethodsController_list` * **Operation**: `GET /payout-methods` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create payout Source: https://docs.lomi.africa/api/payouts/PayoutsUnifiedController_create Create payout *** title: "Create payout" description: "Create payout" full: true method: post path: /payouts operationId: PayoutsUnifiedController\_create --------------------------------------------- ## Overview Create payout Withdraw to a registered payout method (self) or pay a beneficiary on mobile rails (wave/SPI). ### When to use this Use for treasury movements from your lomi. balance. ### Good to know Self payouts require payout\_method\_id; beneficiary wave requires recipient.name and recipient.phone (any mobile number, not payout\_method\_id). Wave rails (self or beneficiary) return 400 on test API keys; live keys only. MTN returns 400 until supported. ### See also [List payouts](/api/payouts/PayoutsUnifiedController_findAll) · [Check available balance](/api/balances/AccountsController_checkAvailableBalance) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /payouts` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/payouts" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `PayoutsUnifiedController_create` * **Operation**: `POST /payouts` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List payouts Source: https://docs.lomi.africa/api/payouts/PayoutsUnifiedController_findAll List payouts *** title: "List payouts" description: "List payouts" full: true method: get path: /payouts operationId: PayoutsUnifiedController\_findAll ---------------------------------------------- ## Overview List payouts Returns withdrawals and beneficiary payouts with a kind discriminator. ### When to use this Use for reconciliation and support. ### See also [Get payout](/api/payouts/PayoutsUnifiedController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /payouts` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ----------- | ----- | -------- | ------ | ----------- | | `pageSize` | query | No | - | | | `page` | query | No | - | | | `endDate` | query | No | - | | | `startDate` | query | No | - | | | `status` | query | No | - | | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/payouts?pageSize=1&page=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `PayoutsUnifiedController_findAll` * **Operation**: `GET /payouts` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get payout Source: https://docs.lomi.africa/api/payouts/PayoutsUnifiedController_findOne Get payout *** title: "Get payout" description: "Get payout" full: true method: get path: /payouts/{id} operationId: PayoutsUnifiedController\_findOne ---------------------------------------------- ## Overview Get payout Returns a single payout by ID scoped to your organization. ### When to use this Use after create or from webhooks. ### See also [Create payout](/api/payouts/PayoutsUnifiedController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /payouts/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | Payout ID | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/payouts/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `PayoutsUnifiedController_findOne` * **Operation**: `GET /payouts/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Add product price Source: https://docs.lomi.africa/api/products/ProductsController_addPrice Add product price *** title: "Add product price" description: "Add product price" full: true method: post path: /products/{id}/prices operationId: ProductsController\_addPrice ----------------------------------------- ## Overview Add product price Adds another price point to an existing product (currency, billing cadence, or amount variants). ### When to use this Use when expanding to new markets or adding a second billing option to the same product. ### See also [Retrieve product](/api/products/ProductsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /products/{id}/prices` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | --------------- | | `id` | Yes | - | UUID du produit | ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/products/value/prices" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `ProductsController_addPrice` * **Operation**: `POST /products/{id}/prices` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Archive product Source: https://docs.lomi.africa/api/products/ProductsController_archive Archive product *** title: "Archive product" description: "Archive product" full: true method: delete path: /products/{id} operationId: ProductsController\_archive ---------------------------------------- ## Overview Archive product Archive le produit (soft delete). ### When to use this Use this endpoint when your flow needs `DELETE /products/{id}`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `DELETE /products/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X DELETE "https://sandbox.api.lomi.africa/products/ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `ProductsController_archive` * **Operation**: `DELETE /products/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create product Source: https://docs.lomi.africa/api/products/ProductsController_create Create product *** title: "Create product" description: "Create product" full: true method: post path: /products operationId: ProductsController\_create --------------------------------------- ## Overview Create product Creates a catalog product with at least one price in a single request. Supports pay\_what\_you\_want via pricing\_model and minimum\_amount/maximum\_amount on nested prices. ### When to use this Use when onboarding catalog data for checkout, subscriptions, or payment links backed by SKUs. ### See also [List products](/api/products/ProductsController_findAll) · [Payment links](/api/payment-links/PaymentLinksController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /products` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/products" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `ProductsController_create` * **Operation**: `POST /products` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List products Source: https://docs.lomi.africa/api/products/ProductsController_findAll List products *** title: "List products" description: "List products" full: true method: get path: /products operationId: ProductsController\_findAll ---------------------------------------- ## Overview List products Returns catalog products with embedded price options. ### When to use this Use to populate storefront admins or pick line items programmatically. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /products` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ---------- | ----- | -------- | ------ | --------------------------- | | `offset` | query | No | - | Décalage pour la pagination | | `limit` | query | No | - | Nombre maximal de résultats | | `isActive` | query | No | - | Filtrer par statut actif | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/products?offset=1&limit=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `ProductsController_findAll` * **Operation**: `GET /products` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve product Source: https://docs.lomi.africa/api/products/ProductsController_findOne Retrieve product *** title: "Retrieve product" description: "Retrieve product" full: true method: get path: /products/{id} operationId: ProductsController\_findOne ---------------------------------------- ## Overview Retrieve product Returns a single product by ID including prices. Responds with **404** when unknown or inaccessible. ### When to use this Use before checkout composition or when validating a stored product ID. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /products/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | --------------- | | `id` | Yes | - | UUID du produit | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/products/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `ProductsController_findOne` * **Operation**: `GET /products/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Set default price Source: https://docs.lomi.africa/api/products/ProductsController_setDefaultPrice Set default price *** title: "Set default price" description: "Set default price" full: true method: post path: /products/{id}/prices/{priceId}/default operationId: ProductsController\_setDefaultPrice ------------------------------------------------ ## Overview Set default price Marks which price lomi. uses when a flow does not specify an explicit price ID. ### When to use this Use after adding multiple prices so checkout and links have a clear fallback. ### See also [Add product price](/api/products/ProductsController_addPrice) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /products/{id}/prices/{priceId}/default` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | --------- | -------- | ------ | --------------- | | `priceId` | Yes | - | UUID du prix | | `id` | Yes | - | UUID du produit | ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | --------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Produit ou prix introuvable | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/products/value/prices/value/default" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `ProductsController_setDefaultPrice` * **Operation**: `POST /products/{id}/prices/{priceId}/default` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update product Source: https://docs.lomi.africa/api/products/ProductsController_update Update product *** title: "Update product" description: "Update product" full: true method: patch path: /products/{id} operationId: ProductsController\_update --------------------------------------- ## Overview Update product Met à jour nom, description, visibilité, images, SKU et stock. ### When to use this Use this endpoint when your flow needs `PATCH /products/{id}`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /products/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/products/ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `ProductsController_update` * **Operation**: `PATCH /products/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List payment providers Source: https://docs.lomi.africa/api/providers/ProvidersController_findAll List payment providers *** title: "List payment providers" description: "List payment providers" full: true method: get path: /providers operationId: ProvidersController\_findAll ----------------------------------------- ## Overview List payment providers Returns connection status for payment providers configured for your organization. ### When to use this Use to show which rails (card, Wave, MTN, SPI) are enabled before rendering checkout options. ### See also [Choose integration](/build/choose-integration) · [Mobile money](/build/mobile-money) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /providers` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | --------------- | ----- | -------- | ------ | ----------------------------------------------------------------------------------------- | | `provider_code` | query | No | - | Filter by provider. Use CARD for card payments (Visa, Mastercard, Apple Pay, Google Pay). | ## Responses | Status | Description | | ------ | ----------------- | | `200` | Provider settings | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/providers?provider_code=CARD" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `ProvidersController_findAll` * **Operation**: `GET /providers` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create refund Source: https://docs.lomi.africa/api/refunds/RefundsController_create Create refund *** title: "Create refund" description: "Create refund" full: true method: post path: /refunds operationId: RefundsController\_create -------------------------------------- ## Overview Create refund Refunds a **completed** transaction on **card**, **Wave**, or **MTN**. Merchant balance updates immediately when the refund is recorded. ### When to use this Use for buyer reversals on eligible completed transactions; supports full and partial amounts. ### Good to know **Card:** customer credit on the card network is completed separately by operations. **Wave partial:** requires a customer phone on file (beneficiary payout). **MTN live:** the original payment must have a provider reference (RequestToPay UUID stored as `provider_checkout_id`); lomi. calls the MTN Disbursement refund API and polls until completion. **MTN test:** ledger-only; no MTN API call. Partial MTN refunds also require a customer phone on file. For subscription-linked payments, pass optional `subscription_action`: `default` (cancel on initial full refund, pause on renewal full refund), `cancel`, `pause`, or `none`. Partial refunds never change the subscription unless the cumulative refund reaches the full transaction amount. ### See also [List refunds](/api/refunds/RefundsController_findAll) · [Retrieve transaction](/api/transactions/TransactionsController_findOne) · [Refunds guide](/build/money/refunds) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /refunds` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `CreateRefundDto` | Field | Required | Type | Description | | --------------------- | -------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | `transaction_id` | Yes | `string` | UUID of a completed transaction (card, Wave, or MTN) | | `amount` | Yes | `number` | Amount to refund (same currency as the transaction) | | `reason` | No | `string` | Reason for the refund | | `refund_type` | No | `enum ("full", "partial")` | Full or partial refund. If omitted, full when amount equals transaction gross amount. | | `subscription_action` | No | `enum ("default", "cancel", "pause", ...)` | Subscription side-effect after a full refund: default (cancel initial payment, pause renewal), cancel, pause, or none. | Example body: ```json { "transaction_id": "string", "amount": 0 } ``` ## Responses | Status | Description | | ------ | ------------------------------------------ | | `201` | Created successfully | | `400` | Bad request, invalid or missing parameters | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/refunds" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"transaction_id":"string","amount":0}' ``` ## OpenAPI * **operationId**: `RefundsController_create` * **Operation**: `POST /refunds` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List refunds Source: https://docs.lomi.africa/api/refunds/RefundsController_findAll List refunds *** title: "List refunds" description: "List refunds" full: true method: get path: /refunds operationId: RefundsController\_findAll --------------------------------------- ## Overview List refunds Returns refunds for your organization with optional status and date filters. ### When to use this Use for reconciliation, support, and dashboards. ### See also [Get refund](/api/refunds/RefundsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /refunds` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ----------- | ----- | -------- | ------ | ----------- | | `offset` | query | No | - | | | `limit` | query | No | - | | | `endDate` | query | No | - | | | `startDate` | query | No | - | | | `status` | query | No | - | | ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/refunds?offset=1&limit=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `RefundsController_findAll` * **Operation**: `GET /refunds` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get refund Source: https://docs.lomi.africa/api/refunds/RefundsController_findOne Get refund *** title: "Get refund" description: "Get refund" full: true method: get path: /refunds/{id} operationId: RefundsController\_findOne --------------------------------------- ## Overview Get refund Returns a single refund by ID scoped to your organization. ### When to use this Use after create or from webhook-driven flows to confirm refund details. ### See also [Create refund](/api/refunds/RefundsController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /refunds/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | Refund ID | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/refunds/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `RefundsController_findOne` * **Operation**: `GET /refunds/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get risk assessment Source: https://docs.lomi.africa/api/risk-assessments/RadarController_findOne Get risk assessment *** title: "Get risk assessment" description: "Get risk assessment" full: true method: get path: /risk-assessments/{id} operationId: RadarController\_findOne ------------------------------------- ## Overview Get risk assessment Returns one Radar assessment by ID, including decision, score, and triggered rule signals. ### When to use this Use when handling `PAYMENT_RISK_FLAGGED` or `PAYMENT_RISK_BLOCKED` webhooks keyed by assessment ID. ### See also [List risk assessments](/api/risk-assessments/RadarController_listAssessments) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /risk-assessments/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------------ | | `id` | Yes | - | Risk assessment ID | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/risk-assessments/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `RadarController_findOne` * **Operation**: `GET /risk-assessments/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List risk assessments Source: https://docs.lomi.africa/api/risk-assessments/RadarController_listAssessments List risk assessments *** title: "List risk assessments" description: "List risk assessments" full: true method: get path: /risk-assessments operationId: RadarController\_listAssessments --------------------------------------------- ## Overview List risk assessments Returns Radar screening results for incoming charges with optional filters for decision, rail, and date range. ### When to use this Use for fraud review queues, exports, and correlating `PAYMENT_RISK_*` webhook payloads. ### See also [Get risk assessment](/api/risk-assessments/RadarController_findOne) · [lomi. Radar guide](/build/money/radar) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /risk-assessments` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ----------- | ----- | -------- | ------ | ----------- | | `pageSize` | query | No | - | | | `page` | query | No | - | | | `endDate` | query | No | - | | | `startDate` | query | No | - | | | `rail` | query | No | - | | | `decision` | query | No | - | | ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ---------------- | | `200` | Risk assessments | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/risk-assessments?pageSize=1&page=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `RadarController_listAssessments` * **Operation**: `GET /risk-assessments` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Request an instant settlement (Nitro) Source: https://docs.lomi.africa/api/settlements/SettlementsController_createInstant Request an instant settlement (Nitro) *** title: "Request an instant settlement (Nitro)" description: "Request an instant settlement (Nitro)" full: true method: post path: /settlements/instant operationId: SettlementsController\_createInstant ------------------------------------------------- ## Overview Request an instant settlement (Nitro) Rail mode records a Nitro fee on an existing payout. Advance mode unlocks held card funds up to the organization cap. Requires `Idempotency-Key`. Advance stays off until ops approves a limit. ### When to use this Use after creating a Wave or SPI payout (rail) or to release held card balance (advance) when Nitro is enabled for the organization. ### Good to know Rail requires `payout_id`. Advance is live-only, excludes disputed transactions, and fails when the org cap is exceeded. Not a loan and not insurance. ### See also [Get instant settlement](/api/settlements/SettlementsController_getInstant) · [Create payout](/api/payouts/PayoutsUnifiedController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /settlements/instant` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | --------------- | -------- | -------------------------- | ------------------------------------------------ | | `mode` | Yes | `enum ("advance", "rail")` | - | | `amount` | Yes | `number` | - | | `currency_code` | Yes | `string` | - | | `payout_id` | No | `string` | Required for rail mode after a payout is created | Example body: ```json { "mode": "advance", "amount": 50000, "currency_code": "XOF" } ``` ## Responses | Status | Description | | ------ | ----------------------------- | | `201` | Instant settlement recorded | | `400` | Ineligible or invalid request | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/settlements/instant" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H "Content-Type: application/json" \ -d '{"mode":"advance","amount":50000,"currency_code":"XOF"}' ``` ## OpenAPI * **operationId**: `SettlementsController_createInstant` * **Operation**: `POST /settlements/instant` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List settlement periods Source: https://docs.lomi.africa/api/settlements/SettlementsController_findAll List settlement periods *** title: "List settlement periods" description: "List settlement periods" full: true method: get path: /settlements operationId: SettlementsController\_findAll ------------------------------------------- ## Overview List settlement periods Returns completed payment totals grouped by availability date (UTC) and currency. Each `settlement_id` uses `{currency}:{YYYY-MM-DD}`. ### When to use this Use for accounting reconciliation before requesting payouts or exporting withdrawable totals by day. ### See also [List settlement transactions](/api/settlements/SettlementsController_findTransactions) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /settlements` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------ | ----- | -------- | ------ | ----------- | | `pageSize` | query | No | - | | | `page` | query | No | - | | | `currency` | query | No | - | | | `end_date` | query | No | - | | | `start_date` | query | No | - | | ## Responses | Status | Description | | ------ | ------------------ | | `200` | Settlement periods | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/settlements?pageSize=1&page=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SettlementsController_findAll` * **Operation**: `GET /settlements` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List settlement transactions Source: https://docs.lomi.africa/api/settlements/SettlementsController_findTransactions List settlement transactions *** title: "List settlement transactions" description: "List settlement transactions" full: true method: get path: /settlements/{id}/transactions operationId: SettlementsController\_findTransactions ---------------------------------------------------- ## Overview List settlement transactions Returns the transactions that contributed to a settlement period identified by `settlement_id`. ### When to use this Use to drill into a settlement row and match ledger movements to individual payments. ### See also [List settlement periods](/api/settlements/SettlementsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /settlements/{id}/transactions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------------------------------------------- | | `id` | Yes | - | Settlement id, format \{currency}:\{YYYY-MM-DD} | ### Query parameters | Name | In | Required | Schema | Description | | ---------- | ----- | -------- | ------ | ----------- | | `pageSize` | query | No | - | | | `page` | query | No | - | | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Transactions in settlement | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/settlements/value/transactions?pageSize=1&page=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SettlementsController_findTransactions` * **Operation**: `GET /settlements/{id}/transactions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get an instant settlement (Nitro request) Source: https://docs.lomi.africa/api/settlements/SettlementsController_getInstant Get an instant settlement (Nitro request) *** title: "Get an instant settlement (Nitro request)" description: "Get an instant settlement (Nitro request)" full: true method: get path: /settlements/instant/{id} operationId: SettlementsController\_getInstant ---------------------------------------------- ## Overview Get an instant settlement (Nitro request) Returns one Nitro instant-settlement request by UUID, scoped to this API key organization. ### When to use this Use after `POST /settlements/instant` or from a webhook to confirm status, fee, and net amount. ### See also [Request instant settlement](/api/settlements/SettlementsController_createInstant) · [List settlement periods](/api/settlements/SettlementsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /settlements/instant/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------------ | | `id` | Yes | - | Nitro request UUID | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | ------------- | | `200` | Nitro request | | `404` | Not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/settlements/instant/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SettlementsController_getInstant` * **Operation**: `GET /settlements/instant/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get checkout settings Source: https://docs.lomi.africa/api/settings/SettingsController_getCheckout Get checkout settings *** title: "Get checkout settings" description: "Get checkout settings" full: true method: get path: /settings/checkout operationId: SettingsController\_getCheckout -------------------------------------------- ## Overview Get checkout settings Langue, URLs, bouton, frais, analytics, champs custom. ### When to use this Use this endpoint when your flow needs `GET /settings/checkout`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /settings/checkout` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/settings/checkout" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SettingsController_getCheckout` * **Operation**: `GET /settings/checkout` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get storefront settings Source: https://docs.lomi.africa/api/settings/SettingsController_getStorefront Get storefront settings *** title: "Get storefront settings" description: "Get storefront settings" full: true method: get path: /settings/storefront operationId: SettingsController\_getStorefront ---------------------------------------------- ## Overview Get storefront settings Activation, slug, annonce, livraison, taxes. ### When to use this Use this endpoint when your flow needs `GET /settings/storefront`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /settings/storefront` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/settings/storefront" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SettingsController_getStorefront` * **Operation**: `GET /settings/storefront` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update checkout settings Source: https://docs.lomi.africa/api/settings/SettingsController_updateCheckout Update checkout settings *** title: "Update checkout settings" description: "Update checkout settings" full: true method: patch path: /settings/checkout operationId: SettingsController\_updateCheckout ----------------------------------------------- ## Overview Update checkout settings Même objet settings que le dashboard. ### When to use this Use this endpoint when your flow needs `PATCH /settings/checkout`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /settings/checkout` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/settings/checkout" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `SettingsController_updateCheckout` * **Operation**: `PATCH /settings/checkout` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update storefront settings Source: https://docs.lomi.africa/api/settings/SettingsController_updateStorefront Update storefront settings *** title: "Update storefront settings" description: "Update storefront settings" full: true method: patch path: /settings/storefront operationId: SettingsController\_updateStorefront ------------------------------------------------- ## Overview Update storefront settings Active, slug, annonce, shipping/tax. ### When to use this Use this endpoint when your flow needs `PATCH /settings/storefront`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /settings/storefront` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/settings/storefront" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `SettingsController_updateStorefront` * **Operation**: `PATCH /settings/storefront` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Close support ticket Source: https://docs.lomi.africa/api/support-requests/SupportRequestsController_close Close support ticket *** title: "Close support ticket" description: "Close support ticket" full: true method: post path: /support-requests/{id}/close operationId: SupportRequestsController\_close --------------------------------------------- ## Overview Close support ticket Marks an open ticket as closed. The creator or an org admin can close it. ### When to use this Use when the issue is resolved and you no longer need a reply. ### See also [Get support ticket](/api/support-requests/SupportRequestsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /support-requests/{id}/close` ## Responses | Status | Description | | ------ | ----------- | | `200` | Closed | ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/support-requests/$TICKET_ID/close" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SupportRequestsController_close` * **Operation**: `POST /support-requests/{id}/close` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create support ticket Source: https://docs.lomi.africa/api/support-requests/SupportRequestsController_create Create support ticket *** title: "Create support ticket" description: "Create support ticket" full: true method: post path: /support-requests operationId: SupportRequestsController\_create ---------------------------------------------- ## Overview Create support ticket Opens a ticket in Settings → Support. The lomi. team sees it in the admin inbox and emails a confirmation. ### When to use this Use when an agent or integration needs to file a complaint or ask for help on a live merchant account. ### See also [List support tickets](/api/support-requests/SupportRequestsController_findAll) · [MCP](/build/mcp) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /support-requests` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Body `category` (`account`, `billing`, `technical`, `feature`, `other`), `message` (required), optional `subject`, and optional context ids (`transaction_id`, `customer_id`, and similar) prepended onto the message. ## Responses | Status | Description | | ------ | -------------- | | `201` | Ticket created | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/support-requests" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"category":"technical","message":"Checkout sessions stay open after a Wave pay.","subject":"Wave checkout hang"}' ``` ## OpenAPI * **operationId**: `SupportRequestsController_create` * **Operation**: `POST /support-requests` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List support tickets Source: https://docs.lomi.africa/api/support-requests/SupportRequestsController_findAll List support tickets *** title: "List support tickets" description: "List support tickets" full: true method: get path: /support-requests operationId: SupportRequestsController\_findAll ----------------------------------------------- ## Overview List support tickets Returns Settings → Support tickets for the organization behind the API key. ### When to use this Use after filing a ticket via MCP or the dashboard to check status and resolution notes. ### See also [Create support ticket](/api/support-requests/SupportRequestsController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /support-requests` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Query parameters | Name | In | Required | Schema | Description | | -------- | ----- | -------- | ------ | ----------- | | `cursor` | query | No | - | | | `limit` | query | No | - | | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/support-requests?limit=20" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SupportRequestsController_findAll` * **Operation**: `GET /support-requests` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get support ticket Source: https://docs.lomi.africa/api/support-requests/SupportRequestsController_findOne Get support ticket *** title: "Get support ticket" description: "Get support ticket" full: true method: get path: /support-requests/{id} operationId: SupportRequestsController\_findOne ----------------------------------------------- ## Overview Get support ticket Returns one ticket by id, including status, subject, and any staff resolution note. ### When to use this Use when following up on a ticket id from create or list. ### See also [List support tickets](/api/support-requests/SupportRequestsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /support-requests/{id}` ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | | `404` | Not found | ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/support-requests/$TICKET_ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SupportRequestsController_findOne` * **Operation**: `GET /support-requests/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Invite team member Source: https://docs.lomi.africa/api/team/TeamController_invite Invite team member *** title: "Invite team member" description: "Invite team member" full: true method: post path: /team/invitations operationId: TeamController\_invite ----------------------------------- ## Overview Invite team member Envoie une invitation. L’humain accepte dans le navigateur. ### When to use this Use this endpoint when your flow needs `POST /team/invitations`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /team/invitations` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/team/invitations" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `TeamController_invite` * **Operation**: `POST /team/invitations` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List team Source: https://docs.lomi.africa/api/team/TeamController_list List team *** title: "List team" description: "List team" full: true method: get path: /team operationId: TeamController\_list --------------------------------- ## Overview List team Membres et invitations de l’organisation. ### When to use this Use this endpoint when your flow needs `GET /team`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /team` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/team" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TeamController_list` * **Operation**: `GET /team` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List team roles Source: https://docs.lomi.africa/api/team/TeamController_listRoles List team roles *** title: "List team roles" description: "List team roles" full: true method: get path: /team/roles operationId: TeamController\_listRoles -------------------------------------- ## Overview List team roles Rôles Admin/Member et rôles custom. ### When to use this Use this endpoint when your flow needs `GET /team/roles`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /team/roles` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/team/roles" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TeamController_listRoles` * **Operation**: `GET /team/roles` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Remove team member Source: https://docs.lomi.africa/api/team/TeamController_remove Remove team member *** title: "Remove team member" description: "Remove team member" full: true method: delete path: /team/members/{memberId} operationId: TeamController\_remove ----------------------------------- ## Overview Remove team member Retire le membre. Pas de self-remove. ### When to use this Use this endpoint when your flow needs `DELETE /team/members/{memberId}`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `DELETE /team/members/{memberId}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X DELETE "https://sandbox.api.lomi.africa/team/members/ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TeamController_remove` * **Operation**: `DELETE /team/members/{memberId}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Revoke invitation Source: https://docs.lomi.africa/api/team/TeamController_revokeInvite Revoke invitation *** title: "Revoke invitation" description: "Revoke invitation" full: true method: delete path: /team/invitations operationId: TeamController\_revokeInvite ----------------------------------------- ## Overview Revoke invitation Annule une invitation en attente. ### When to use this Use this endpoint when your flow needs `DELETE /team/invitations`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `DELETE /team/invitations` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X DELETE "https://sandbox.api.lomi.africa/team/invitations" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TeamController_revokeInvite` * **Operation**: `DELETE /team/invitations` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update member role Source: https://docs.lomi.africa/api/team/TeamController_updateRole Update member role *** title: "Update member role" description: "Update member role" full: true method: patch path: /team/members/{memberId} operationId: TeamController\_updateRole --------------------------------------- ## Overview Update member role Admin/Member ou role\_id. ### When to use this Use this endpoint when your flow needs `PATCH /team/members/{memberId}`. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /team/members/{memberId}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/team/members/ID" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `TeamController_updateRole` * **Operation**: `PATCH /team/members/{memberId}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List transactions Source: https://docs.lomi.africa/api/transactions/TransactionsController_findAll List transactions *** title: "List transactions" description: "List transactions" full: true method: get path: /transactions operationId: TransactionsController\_findAll -------------------------------------------- ## Overview List transactions Returns ledger transactions with filters for status, provider, method, currency, and time range. ### When to use this Use as the primary reconciliation feed for payments, refunds, and payouts visible to your org. ### See also See also [Payment and payout lifecycle](/build/reliability/payment-lifecycle) for status semantics. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /transactions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | --------------- | ----- | -------- | ------ | --------------------------------------------------------------------------------------- | | `isPos` | query | No | - | Uniquement les transactions points de vente (TPV) | | `endDate` | query | No | - | Jusqu'à cette date (format ISO 8601) | | `startDate` | query | No | - | À partir de cette date (format ISO 8601) | | `pageSize` | query | No | - | Nombre d'éléments par page | | `page` | query | No | - | Numéro de page | | `paymentMethod` | query | No | - | Filtrer par code de moyen de paiement (séparés par des virgules pour plusieurs valeurs) | | `currency` | query | No | - | Filtrer par code devise (séparés par des virgules pour plusieurs valeurs) | | `type` | query | No | - | Filtrer par type de transaction (séparés par des virgules pour plusieurs valeurs) | | `status` | query | No | - | Filtrer par statut de transaction (séparés par des virgules pour plusieurs valeurs) | | `provider` | query | No | - | Filtrer par code de fournisseur de paiement | ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/transactions?isPos=true&endDate=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TransactionsController_findAll` * **Operation**: `GET /transactions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve transaction Source: https://docs.lomi.africa/api/transactions/TransactionsController_findOne Retrieve transaction *** title: "Retrieve transaction" description: "Retrieve transaction" full: true method: get path: /transactions/{id} operationId: TransactionsController\_findOne -------------------------------------------- ## Overview Retrieve transaction Returns one transaction by ID. Responds with **404** when unknown or inaccessible. ### When to use this Use for receipt screens, support tickets, and webhook-triggered deep links. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /transactions/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ---------------------- | | `id` | Yes | - | UUID de la transaction | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/transactions/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TransactionsController_findOne` * **Operation**: `GET /transactions/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Download receipt PDF Source: https://docs.lomi.africa/api/transactions/TransactionsController_receiptPdf Download receipt PDF *** title: "Download receipt PDF" description: "Download receipt PDF" full: true method: get path: /transactions/{id}/receipt.pdf operationId: TransactionsController\_receiptPdf ----------------------------------------------- ## Overview Download receipt PDF Returns hosted\_url and download\_url for the transaction receipt PDF. ### When to use this Use this endpoint when your flow needs `GET /transactions/{id}/receipt.pdf` (Download receipt PDF). ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /transactions/{id}/receipt.pdf` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ---------------------- | | `id` | Yes | - | UUID de la transaction | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `Lomi-Account` | header | No | - | Optional lomi. Network account id (`acct_...`). When present, the API key acts as the Operator and the request targets the connected Member Account. | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/transactions/value/receipt.pdf" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TransactionsController_receiptPdf` * **Operation**: `GET /transactions/{id}/receipt.pdf` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create transfer Source: https://docs.lomi.africa/api/transfers/TransfersController_create Create transfer *** title: "Create transfer" description: "Create transfer" full: true method: post path: /transfers operationId: TransfersController\_create ---------------------------------------- ## Overview Create transfer Moves funds from your Operator balance to a connected Member Account (`acct_...`). Two-step confirmation: the first call returns `requires_confirmation: true` and a `confirmation_token`; repeat the same request with `confirmation_token` to execute. Balances move in XOF. ### When to use this Use for lomi. Network separate charges and transfers: charge on your own account first, then pay the member later (after delivery, at the end of the day, or in a batch). ### Good to know Operator secret key **without** `Lomi-Account`. `Idempotency-Key` is required. The destination must be an active membership with `transfer.receive` for the key environment. A transfer cannot exceed your available balance. ### See also [List transfers](/api/transfers/TransfersController_findAll) · [Reverse transfer](/api/transfers/TransfersController_reverse) · [lomi. Network guide](/build/platform/network#transfers) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /transfers` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | ----------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Idempotency-Key` | header | Yes | - | Required unique key for this write. Replays return the original response. | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body JSON request payload. Schema: `CreateTransferDto` | Field | Required | Type | Description | | ----------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | | `amount` | Yes | `number` | Amount to transfer, in `currency_code` (integer units). | | `currency_code` | Yes | `string` | Currency of the transfer (XOF, USD, EUR). Balances settle in XOF. | | `destination` | Yes | `string` | Member Account that receives the funds. Must be an active membership with the transfer.receive capability for this environment. | | `transfer_group` | No | `string` | Transfer group linking this transfer to the payment(s) it settles (separate charges and transfers). | | `source_transaction_id` | No | `string` | Transaction this transfer settles (optional, for reporting and refunds). | | `description` | No | `string` | - | | `metadata` | No | `object` | - | Example body: ```json { "amount": 9000, "currency_code": "XOF", "destination": "acct_1a2b3c4d5e6f7g8h" } ``` ## Responses | Status | Description | | ------ | -------------------------------------------------------- | | `201` | Transfer created (object: transfer) | | `400` | Invalid destination, capability, or insufficient balance | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/transfers" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":9000,"currency_code":"XOF","destination":"acct_1a2b3c4d5e6f7g8h"}' ``` ## OpenAPI * **operationId**: `TransfersController_create` * **Operation**: `POST /transfers` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List transfers Source: https://docs.lomi.africa/api/transfers/TransfersController_findAll List transfers *** title: "List transfers" description: "List transfers" full: true method: get path: /transfers operationId: TransfersController\_findAll ----------------------------------------- ## Overview List transfers Returns transfers created by your Operator organization, including destination transfers, separate transfers, settled operator fees, and reversals. ### When to use this Use for reconciliation by `transfer_group` or `destination`, or to build a per-member statement. Filter by `transfer_type` to isolate fees or reversals. ### See also [Retrieve transfer](/api/transfers/TransfersController_findOne) · [Create transfer](/api/transfers/TransfersController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /transfers` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ----------------------- | ----- | -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- | | `limit` | query | No | - | | | `cursor` | query | No | - | | | `transfer_type` | query | No | - | Comma-separated: destination, separate, operator\_fee, processing\_fee\_cover, fee\_reversal, transfer\_reversal, loss\_cover | | `source_transaction_id` | query | No | - | | | `transfer_group` | query | No | - | | | `destination` | query | No | - | | ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | --------------------------- | | `200` | Paginated list of transfers | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/transfers?limit=1&cursor=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TransfersController_findAll` * **Operation**: `GET /transfers` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve transfer Source: https://docs.lomi.africa/api/transfers/TransfersController_findOne Retrieve transfer *** title: "Retrieve transfer" description: "Retrieve transfer" full: true method: get path: /transfers/{id} operationId: TransfersController\_findOne ----------------------------------------- ## Overview Retrieve transfer Returns a single transfer (`tr_...`) with its status, settled amount, source transaction, and reversed amount. ### When to use this Use after create or from a `NETWORK_TRANSFER_CREATED` webhook to confirm the transfer landed on the member balance. ### See also [List transfers](/api/transfers/TransfersController_findAll) · [Reverse transfer](/api/transfers/TransfersController_reverse) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /transfers/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | --------------------- | | `id` | Yes | - | Transfer id (tr\_...) | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ------------------ | | `200` | Transfer object | | `404` | Transfer not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/transfers/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `TransfersController_findOne` * **Operation**: `GET /transfers/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Reverse transfer Source: https://docs.lomi.africa/api/transfers/TransfersController_reverse Reverse transfer *** title: "Reverse transfer" description: "Reverse transfer" full: true method: post path: /transfers/{id}/reversals operationId: TransfersController\_reverse ----------------------------------------- ## Overview Reverse transfer Pulls funds back from the Member Account to your Operator balance. Defaults to the remaining unreversed amount; pass `amount` for a partial reversal. Same two-step `confirmation_token` flow as create. ### When to use this Use when a separate transfer was too large or an order was cancelled after you paid the member. Refunds on destination and separate charges reverse transfers automatically (`reverse_transfer`). ### Good to know The member must have enough available balance to cover the reversal. `Idempotency-Key` is required. Emits `NETWORK_TRANSFER_REVERSED`. ### See also [Create transfer](/api/transfers/TransfersController_create) · [Create refund](/api/refunds/RefundsController_create) · [lomi. Network guide](/build/platform/network#refunds-and-liability) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /transfers/{id}/reversals` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | --------------------- | | `id` | Yes | - | Transfer id (tr\_...) | ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | ----------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Idempotency-Key` | header | Yes | - | Required unique key for this write. Replays return the original response. | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ### Request body JSON request payload. Schema: `CreateTransferReversalDto` | Field | Required | Type | Description | | ------------- | -------- | -------- | ----------------------------------------------------------------------------------------- | | `amount` | No | `number` | Amount to reverse, in the transfer currency. Defaults to the remaining unreversed amount. | | `description` | No | `string` | - | | `metadata` | No | `object` | - | Example body: ```json { "amount": 4500, "description": "Order 95 refunded", "metadata": {} } ``` ## Responses | Status | Description | | ------ | ------------------------- | | `201` | Reversal transfer created | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/transfers/value/reversals" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":4500,"description":"Order 95 refunded","metadata":{}}' ``` ## OpenAPI * **operationId**: `TransfersController_reverse` * **Operation**: `POST /transfers/{id}/reversals` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List webhook delivery logs Source: https://docs.lomi.africa/api/webhooks/WebhookDeliveryLogsController_findAll List webhook delivery logs *** title: "List webhook delivery logs" description: "List webhook delivery logs" full: true method: get path: /webhooks/deliveries operationId: WebhookDeliveryLogsController\_findAll --------------------------------------------------- ## Overview List webhook delivery logs Returns delivery attempts for an outbound webhook endpoint, including HTTP status and retry hints. ### When to use this Use when debugging missed events or proving delivery to auditors. ### See also [Retrieve webhook](/api/webhooks/WebhooksController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /webhooks/deliveries` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------- | ----- | -------- | ------ | ----------------------------------------- | | `offset` | query | No | - | Nombre de journaux à ignorer (pagination) | | `limit` | query | No | - | Nombre maximal de journaux | | `failedOnly` | query | No | - | Uniquement les livraisons en échec | | `successOnly` | query | No | - | Uniquement les livraisons réussies | | `webhookId` | query | Yes | - | Filtrer par identifiant de webhook | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/webhooks/deliveries?offset=1&limit=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `WebhookDeliveryLogsController_findAll` * **Operation**: `GET /webhooks/deliveries` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve webhook delivery log Source: https://docs.lomi.africa/api/webhooks/WebhookDeliveryLogsController_findOne Retrieve webhook delivery log *** title: "Retrieve webhook delivery log" description: "Retrieve webhook delivery log" full: true method: get path: /webhooks/deliveries/{id} operationId: WebhookDeliveryLogsController\_findOne --------------------------------------------------- ## Overview Retrieve webhook delivery log Returns a single delivery attempt record. ### When to use this Use when correlating one failure with a specific HTTP response body your server returned. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /webhooks/deliveries/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | --------------- | | `id` | Yes | - | UUID du journal | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/webhooks/deliveries/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `WebhookDeliveryLogsController_findOne` * **Operation**: `GET /webhooks/deliveries/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create webhook Source: https://docs.lomi.africa/api/webhooks/WebhooksController_create Create webhook *** title: "Create webhook" description: "Create webhook" full: true method: post path: /webhooks operationId: WebhooksController\_create --------------------------------------- ## Overview Create webhook Registers an outbound HTTPS endpoint and the event types you want delivered. ### When to use this Use once per environment when wiring your server to lomi. event notifications. ### Good to know Store the signing secret securely; verify signatures on every inbound request. ### See also [List webhooks](/api/webhooks/WebhooksController_findAll) · [Test webhook](/api/webhooks/WebhooksController_test) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /webhooks` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/webhooks" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `WebhooksController_create` * **Operation**: `POST /webhooks` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List webhooks Source: https://docs.lomi.africa/api/webhooks/WebhooksController_findAll List webhooks *** title: "List webhooks" description: "List webhooks" full: true method: get path: /webhooks operationId: WebhooksController\_findAll ---------------------------------------- ## Overview List webhooks Returns configured outbound webhook subscriptions (URL, events, signing configuration). ### When to use this Use during setup to confirm which environments receive production traffic. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /webhooks` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/webhooks" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `WebhooksController_findAll` * **Operation**: `GET /webhooks` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve webhook Source: https://docs.lomi.africa/api/webhooks/WebhooksController_findOne Retrieve webhook *** title: "Retrieve webhook" description: "Retrieve webhook" full: true method: get path: /webhooks/{id} operationId: WebhooksController\_findOne ---------------------------------------- ## Overview Retrieve webhook Returns one outbound subscription by ID for editing forms. ### When to use this Use before rotating secrets or changing the event filter. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /webhooks/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Le webhook | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/webhooks/id_value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `WebhooksController_findOne` * **Operation**: `GET /webhooks/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Delete webhook Source: https://docs.lomi.africa/api/webhooks/WebhooksController_remove Delete webhook *** title: "Delete webhook" description: "Delete webhook" full: true method: delete path: /webhooks/{id} operationId: WebhooksController\_remove --------------------------------------- ## Overview Delete webhook Removes an outbound webhook subscription; deliveries stop for that endpoint. ### When to use this Use when decommissioning an environment or rotating to a new endpoint record. ### See also [Create webhook](/api/webhooks/WebhooksController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `DELETE /webhooks/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------ | | `id` | Yes | - | Webhook UUID | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X DELETE "https://sandbox.api.lomi.africa/webhooks/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `WebhooksController_remove` * **Operation**: `DELETE /webhooks/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retry webhook delivery Source: https://docs.lomi.africa/api/webhooks/WebhooksController_retryDelivery Retry webhook delivery *** title: "Retry webhook delivery" description: "Retry webhook delivery" full: true method: post path: /webhooks/{id}/deliveries/{deliveryId}/retry operationId: WebhooksController\_retryDelivery ---------------------------------------------- ## Overview Retry webhook delivery Re-sends a single failed delivery attempt for debugging after you fix your receiver. ### When to use this Use from support tools; not a substitute for idempotent handling on your server. ### See also [Webhook delivery logs](/api/webhooks/WebhookDeliveryLogsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /webhooks/{id}/deliveries/{deliveryId}/retry` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ------------ | -------- | ------ | ----------------- | | `deliveryId` | Yes | - | Delivery log UUID | | `id` | Yes | - | Webhook UUID | ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/webhooks/value/deliveries/value/retry" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `WebhooksController_retryDelivery` * **Operation**: `POST /webhooks/{id}/deliveries/{deliveryId}/retry` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Test webhook Source: https://docs.lomi.africa/api/webhooks/WebhooksController_test Test webhook *** title: "Test webhook" description: "Test webhook" full: true method: post path: /webhooks/{id}/test operationId: WebhooksController\_test ------------------------------------- ## Overview Test webhook Sends a sample event to the configured URL so you can validate signature verification and parsing. ### When to use this Use immediately after creating or updating a webhook endpoint. ### See also [Create webhook](/api/webhooks/WebhooksController_create) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /webhooks/{id}/test` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ------------ | | `id` | Yes | - | Webhook UUID | ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/webhooks/value/test" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `WebhooksController_test` * **Operation**: `POST /webhooks/{id}/test` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update webhook Source: https://docs.lomi.africa/api/webhooks/WebhooksController_update Update webhook *** title: "Update webhook" description: "Update webhook" full: true method: patch path: /webhooks/{id} operationId: WebhooksController\_update --------------------------------------- ## Overview Update webhook Patches delivery URL, secrets, subscribed events, or lifecycle flags for an existing subscription. ### When to use this Use when rotating signing secrets without re-creating the endpoint record. ### Good to know Coordinate secret rotation with your receiver to avoid rejecting signed payloads. ### See also [Webhook delivery logs](/api/webhooks/WebhookDeliveryLogsController_findAll) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /webhooks/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | ------------------- | -------- | --------------- | ----------- | | `url` | No | `string` | - | | `is_active` | No | `boolean` | - | | `authorized_events` | No | `array` | - | | `metadata` | No | `object` | - | Example body: ```json { "url": "string", "is_active": true, "authorized_events": [ "string" ] } ``` ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/webhooks/id_value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"string","is_active":true,"authorized_events":["string"]}' ``` ## OpenAPI * **operationId**: `WebhooksController_update` * **Operation**: `PATCH /webhooks/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Webhooks Source: https://docs.lomi.africa/api/webhooks Create webhook endpoints, send test events, inspect delivery logs, and retry failed deliveries. *** title: 'Webhooks' description: 'Create webhook endpoints, send test events, inspect delivery logs, and retry failed deliveries.' index: true ----------- Webhooks are the operational source of truth for events that finish outside the browser redirect path. This section includes endpoint management and delivery-log operations. ## What belongs here * Create and update webhook endpoints. * Send a test event to validate your handler. * List delivery logs when debugging. * Retry a failed delivery from a known log. * Remove endpoints that should no longer receive events. ## Reliability notes Verify signatures with the raw request body, store processed event IDs, and make fulfillment idempotent. Redirects are useful for customer experience, but webhooks are safer for final reconciliation. ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. # Check customer entitlement Source: https://docs.lomi.africa/api/usage/UsageBillingController_checkEntitlement Check customer entitlement *** title: "Check customer entitlement" description: "Check customer entitlement" full: true method: get path: /usage/entitlements operationId: UsageBillingController\_checkEntitlement ----------------------------------------------------- ## Overview Check customer entitlement Returns whether a customer has an active entitlement for the given `feature_key`. ### When to use this Use at request time to gate features without loading full subscription objects. ### See also [Create entitlement](/api/usage/UsageBillingController_createEntitlement) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /usage/entitlements` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------- | ----- | -------- | ------ | ----------- | | `feature_key` | query | Yes | - | | | `customer_id` | query | Yes | - | | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/usage/entitlements?feature_key=value&customer_id=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `UsageBillingController_checkEntitlement` * **Operation**: `GET /usage/entitlements` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create or update an entitlement Source: https://docs.lomi.africa/api/usage/UsageBillingController_createEntitlement Create or update an entitlement *** title: "Create or update an entitlement" description: "Create or update an entitlement" full: true method: post path: /usage/entitlements operationId: UsageBillingController\_createEntitlement ------------------------------------------------------ ## Overview Create or update an entitlement Defines a plan entitlement feature keyed by `feature_key` for usage or access gating. ### When to use this Use when feature access is tied to plan entitlements rather than raw meter balance alone. ### See also [Check entitlement](/api/usage/UsageBillingController_checkEntitlement) · [Usage billing guide](/build/billing/usage-billing) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /usage/entitlements` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/usage/entitlements" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `UsageBillingController_createEntitlement` * **Operation**: `POST /usage/entitlements` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Credit prepaid usage units Source: https://docs.lomi.africa/api/usage/UsageBillingController_creditWallet Credit prepaid usage units *** title: "Credit prepaid usage units" description: "Credit prepaid usage units" full: true method: post path: /usage/credits operationId: UsageBillingController\_creditWallet ------------------------------------------------- ## Overview Credit prepaid usage units Adds credited units to a customer meter wallet (prepaid or promotional credits). ### When to use this Use for prepaid packs, promotions, or manual adjustments before usage draws down balance. ### See also [Get meter balance](/api/meters/MetersController_getBalance) · [Record usage event](/api/usage/UsageEventsController_ingest) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /usage/credits` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/usage/credits" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `UsageBillingController_creditWallet` * **Operation**: `POST /usage/credits` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Combined revenue metrics Source: https://docs.lomi.africa/api/usage/UsageBillingController_getRevenue Combined revenue metrics *** title: "Combined revenue metrics" description: "Combined revenue metrics" full: true method: get path: /usage/revenue operationId: UsageBillingController\_getRevenue ----------------------------------------------- ## Overview Combined revenue metrics Returns MRR, usage revenue, and one-time revenue for a date range. ### When to use this Use for finance reporting that combines subscription MRR with metered usage and one-off charges. ### Good to know Requires `start_date` and `end_date` query parameters. ### See also [Organization metrics](/api/organizations/OrganizationsController_getMetrics) · [Usage billing guide](/build/billing/usage-billing) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /usage/revenue` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------ | ----- | -------- | ------ | ----------- | | `end_date` | query | Yes | - | | | `start_date` | query | Yes | - | | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/usage/revenue?end_date=value&start_date=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `UsageBillingController_getRevenue` * **Operation**: `GET /usage/revenue` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List usage billing periods Source: https://docs.lomi.africa/api/usage/UsageBillingController_listPeriods List usage billing periods *** title: "List usage billing periods" description: "List usage billing periods" full: true method: get path: /usage/periods operationId: UsageBillingController\_listPeriods ------------------------------------------------ ## Overview List usage billing periods Returns billing periods for usage subscriptions, optionally filtered by subscription ID. ### When to use this Use for invoicing windows, period-close reconciliation, or support lookups. ### See also [Get subscription usage](/api/subscriptions/SubscriptionsController_getUsage) · [Usage billing guide](/build/billing/usage-billing) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /usage/periods` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ----------------- | ----- | -------- | ------ | ----------- | | `page_size` | query | No | - | | | `page` | query | No | - | | | `subscription_id` | query | No | - | | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/usage/periods?page_size=1&page=1" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `UsageBillingController_listPeriods` * **Operation**: `GET /usage/periods` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Create a usage subscription Source: https://docs.lomi.africa/api/usage/UsageEventsController_createUsageSubscription Create a usage subscription *** title: "Create a usage subscription" description: "Create a usage subscription" full: true method: post path: /usage/subscriptions operationId: UsageEventsController\_createUsageSubscription ----------------------------------------------------------- ## Overview Create a usage subscription Enrolls a customer on a `usage_based` product without an upfront charge. Required before billing metered usage to that customer. ### When to use this After creating a usage-based product and meter; enroll each customer before sending usage events tied to a subscription. ### See also [Usage billing guide](/build/billing/usage-billing) · [Products guide](/build/billing/products) · [Subscription usage](/api/subscriptions/SubscriptionsController_getUsage) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /usage/subscriptions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | -------------------- | | `201` | Created successfully | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/usage/subscriptions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `UsageEventsController_createUsageSubscription` * **Operation**: `POST /usage/subscriptions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List usage events Source: https://docs.lomi.africa/api/usage/UsageEventsController_findAll List usage events *** title: "List usage events" description: "List usage events" full: true method: get path: /usage/events operationId: UsageEventsController\_findAll ------------------------------------------- ## Overview List usage events Lists ingested usage events with pagination and optional filters for customer, code, and processing status. ### When to use this Use for support, reconciliation, or debugging failed usage ingest. ### See also [Record usage event](/api/usage/UsageEventsController_ingest) · [Get usage event](/api/usage/UsageEventsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /usage/events` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------- | ----- | -------- | ------ | ----------- | | `status` | query | No | - | | | `code` | query | No | - | | | `customer_id` | query | No | - | | | `page_size` | query | No | - | | | `page` | query | No | - | | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/usage/events?status=pending&code=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `UsageEventsController_findAll` * **Operation**: `GET /usage/events` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get a usage event Source: https://docs.lomi.africa/api/usage/UsageEventsController_findOne Get a usage event *** title: "Get a usage event" description: "Get a usage event" full: true method: get path: /usage/events/{id} operationId: UsageEventsController\_findOne ------------------------------------------- ## Overview Get a usage event Returns one usage event by ID, including processing status and error details when failed. ### When to use this Use after ingest to confirm processing or investigate a specific event. ### See also [List usage events](/api/usage/UsageEventsController_findAll) · [Record usage event](/api/usage/UsageEventsController_ingest) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /usage/events/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | ----------- | | `id` | Yes | - | Event ID | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/usage/events/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `UsageEventsController_findOne` * **Operation**: `GET /usage/events/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Record a usage event Source: https://docs.lomi.africa/api/usage/UsageEventsController_ingest Record a usage event *** title: "Record a usage event" description: "Record a usage event" full: true method: post path: /usage/events operationId: UsageEventsController\_ingest ------------------------------------------ ## Overview Record a usage event Idempotent usage ingest. Events are processed asynchronously and update meter balances when matched. ### When to use this Call from your app whenever billable usage occurs; use a stable `transaction_id` per logical event. ### Good to know Returns `202 Accepted`. Confirm `processing_status` via webhooks or polling `GET /usage/events/{id}`. ### See also [Usage billing guide](/build/billing/usage-billing) · [Create meter](/api/meters/MetersController_create) · [Create usage subscription](/api/usage/UsageEventsController_createUsageSubscription) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /usage/events` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ----------- | | `202` | Accepted | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/usage/events" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `UsageEventsController_ingest` * **Operation**: `POST /usage/events` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Cancel subscription Source: https://docs.lomi.africa/api/subscriptions/SubscriptionsController_cancel Cancel subscription *** title: "Cancel subscription" description: "Cancel subscription" full: true method: post path: /subscriptions/{id}/cancel operationId: SubscriptionsController\_cancel -------------------------------------------- ## Overview Cancel subscription Cancels an active subscription; optional reason is stored for analytics and chargeback context. ### When to use this Use when the customer ends service or you enforce policy cancellations. ### See also [Retrieve subscription](/api/subscriptions/SubscriptionsController_findOne) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /subscriptions/{id}/cancel` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------------- | | `id` | Yes | - | UUID de l’abonnement | ### Query parameters *No query parameters.* ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | ---------------------- | -------- | --------- | ----------- | | `cancel_at_period_end` | No | `boolean` | - | | `cancellation_reason` | No | `string` | - | Example body: ```json { "cancel_at_period_end": true, "cancellation_reason": "string" } ``` ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/subscriptions/value/cancel" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"cancel_at_period_end":true,"cancellation_reason":"string"}' ``` ## OpenAPI * **operationId**: `SubscriptionsController_cancel` * **Operation**: `POST /subscriptions/{id}/cancel` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Change subscription plan Source: https://docs.lomi.africa/api/subscriptions/SubscriptionsController_changePlan Change subscription plan *** title: "Change subscription plan" description: "Change subscription plan" full: true method: post path: /subscriptions/{id}/change-plan operationId: SubscriptionsController\_changePlan ------------------------------------------------ ## Overview Change subscription plan Updates the `price_id` on an active subscription for upgrades or downgrades. ### When to use this Use when moving a customer to a different recurring price on the same product line. ### See also [Retrieve subscription](/api/subscriptions/SubscriptionsController_findOne) · [Update subscription](/api/subscriptions/SubscriptionsController_update) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /subscriptions/{id}/change-plan` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------------- | | `id` | Yes | - | UUID de l’abonnement | ### Query parameters *No query parameters.* ### Request body JSON request payload. Schema: `object` | Field | Required | Type | Description | | ---------- | -------- | -------- | ----------- | | `price_id` | Yes | `string` | - | Example body: ```json { "price_id": "string" } ``` ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/subscriptions/value/change-plan" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"price_id":"string"}' ``` ## OpenAPI * **operationId**: `SubscriptionsController_changePlan` * **Operation**: `POST /subscriptions/{id}/change-plan` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # List subscriptions Source: https://docs.lomi.africa/api/subscriptions/SubscriptionsController_findAll List subscriptions *** title: "List subscriptions" description: "List subscriptions" full: true method: get path: /subscriptions operationId: SubscriptionsController\_findAll --------------------------------------------- ## Overview List subscriptions Returns subscriptions for your organization. Optional `customer_id` and `status` query filters narrow the list. ### When to use this Use for billing ops, dunning dashboards, and revenue reporting. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /subscriptions` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters | Name | In | Required | Schema | Description | | ------------- | ----- | -------- | ------ | ------------------------------------------------------- | | `status` | query | No | - | Filtrer par statut (active, cancelled, past\_due, etc.) | | `customer_id` | query | No | - | Filtrer par UUID client | | `pageSize` | query | No | - | Nombre d'éléments par page | | `page` | query | No | - | Numéro de page | ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/subscriptions?status=value&customer_id=value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SubscriptionsController_findAll` * **Operation**: `GET /subscriptions` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Retrieve subscription Source: https://docs.lomi.africa/api/subscriptions/SubscriptionsController_findOne Retrieve subscription *** title: "Retrieve subscription" description: "Retrieve subscription" full: true method: get path: /subscriptions/{id} operationId: SubscriptionsController\_findOne --------------------------------------------- ## Overview Retrieve subscription Returns one subscription by ID including cycle and price references. Responds with **404** when unknown or inaccessible. ### When to use this Use before upgrades, cancelations, or invoicing integration. ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /subscriptions/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------------- | | `id` | Yes | - | UUID de l’abonnement | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | -------------------------- | | `200` | Success | | `401` | Invalid or missing API key | | `404` | Resource not found | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/subscriptions/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SubscriptionsController_findOne` * **Operation**: `GET /subscriptions/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Get meter usage for a subscription Source: https://docs.lomi.africa/api/subscriptions/SubscriptionsController_getUsage Get meter usage for a subscription *** title: "Get meter usage for a subscription" description: "Get meter usage for a subscription" full: true method: get path: /subscriptions/{id}/usage operationId: SubscriptionsController\_getUsage ---------------------------------------------- ## Overview Get meter usage for a subscription Returns aggregated meter usage for a usage subscription across its billing period. ### When to use this Use on invoices, customer usage dashboards, or before closing a billing period. ### See also [List billing periods](/api/usage/UsageBillingController_listPeriods) · [Record usage event](/api/usage/UsageEventsController_ingest) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `GET /subscriptions/{id}/usage` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------------- | | `id` | Yes | - | UUID de l’abonnement | ### Query parameters *No query parameters.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X GET "https://sandbox.api.lomi.africa/subscriptions/value/usage" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ## OpenAPI * **operationId**: `SubscriptionsController_getUsage` * **Operation**: `GET /subscriptions/{id}/usage` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Resume subscription Source: https://docs.lomi.africa/api/subscriptions/SubscriptionsController_resume Resume subscription *** title: "Resume subscription" description: "Resume subscription" full: true method: post path: /subscriptions/{id}/resume operationId: SubscriptionsController\_resume -------------------------------------------- ## Overview Resume subscription Removes a scheduled end-of-period cancellation so the subscription keeps renewing. ### When to use this Use when a customer reverses a pending cancel-at-period-end before the billing period ends. ### See also [Cancel subscription](/api/subscriptions/SubscriptionsController_cancel) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `POST /subscriptions/{id}/resume` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------------- | | `id` | Yes | - | UUID de l’abonnement | ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/subscriptions/value/resume" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `SubscriptionsController_resume` * **Operation**: `POST /subscriptions/{id}/resume` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Update subscription Source: https://docs.lomi.africa/api/subscriptions/SubscriptionsController_update Update subscription *** title: "Update subscription" description: "Update subscription" full: true method: patch path: /subscriptions/{id} operationId: SubscriptionsController\_update -------------------------------------------- ## Overview Update subscription Patches an organization subscription (metadata, price, or fields supported by the API). ### When to use this Use for plan changes initiated from your admin tools or customer portal backends. ### See also [Retrieve subscription](/api/subscriptions/SubscriptionsController_findOne) · [Cancel subscription](/api/subscriptions/SubscriptionsController_cancel) ## Authentication Merchant routes require an API key in the `X-API-KEY` header (see [Integration overview](/api)). Use a **test** key against `https://sandbox.api.lomi.africa` and a **live** key against `https://api.lomi.africa`. ## Endpoint `PATCH /subscriptions/{id}` Base URLs: * `https://sandbox.api.lomi.africa` * `https://api.lomi.africa` ## Request ### Path parameters | Name | Required | Schema | Description | | ---- | -------- | ------ | -------------------- | | `id` | Yes | - | UUID de l’abonnement | ### Query parameters *No query parameters.* ### Request body *No application/json body for this operation.* ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. ## Example ```bash curl -sS -X PATCH "https://sandbox.api.lomi.africa/subscriptions/value" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ## OpenAPI * **operationId**: `SubscriptionsController_update` * **Operation**: `PATCH /subscriptions/{id}` Full schemas and **Try it**: [API reference](/api). Machine-readable contract: repo `apps/docs/openapi.json`. # Subscriptions Source: https://docs.lomi.africa/api/subscriptions Manage recurring subscriptions and customer-scoped subscription operations. *** title: 'Subscriptions' description: 'Manage recurring subscriptions and customer-scoped subscription operations.' index: true ----------- Subscriptions cover recurring billing created through checkout, payment links, or backend workflows. Customer-scoped subscription operations live here too, so the API reference has one place for recurring billing. ## What belongs here * List and retrieve subscription instances. * Update subscription metadata or supported fields. * Cancel a subscription. * Inspect subscriptions for a specific customer. * Use customer-scoped operations when your customer portal or admin tools need them. ## Related guides * [Build subscriptions](/build/billing/subscriptions) * [Customer portal](/build/billing/customer-portal) * [Checkout behavior](/build/accept/checkout-behavior) ## Request ### Path parameters *No path parameters beyond the URL pattern.* ### Query parameters *No query parameters.* ### Headers | Name | In | Required | Schema | Description | | -------------- | ------ | -------- | ------ | ---------------------------------------------------------------------------------------------------- | | `Lomi-Version` | header | No | - | Optional schema version pin. Echoes OpenAPI info.version (currently 1.2.0). Routes stay unversioned. | ## Responses | Status | Description | | ------ | ----------- | | `200` | Success | ## Errors Errors follow the standard JSON error format (status code and machine-readable message). Validate inputs before calling; **401** indicates a missing/invalid key, **404** a missing resource for this organization, **429** rate limiting. For safe retries on create-style calls, send an idempotency key when your flow supports it. # Portal del cliente Source: https://docs.lomi.africa/build/billing/customer-portal.es Portal alojado con sesiones de lanzamiento, autenticación OTP y acceso del cliente. *** title: Portal del cliente description: Portal alojado con sesiones de lanzamiento, autenticación OTP y acceso del cliente. ------------------------------------------------------------------------------------------------ Consulte la guía en inglés para los detalles completos de integración: [Customer portal (EN)](/build/billing/customer-portal). Resumen: * URL por defecto: `https://customers.lomi.africa/o/{slug-de-su-org}` * Lanzamiento preautenticado: `POST /customers/{id}/portal` → `launch_url` (sin segundo OTP si hay `customer_id`) * Correos de recibo incluyen el enlace al portal de la organización # Customer portal Source: https://docs.lomi.africa/build/billing/customer-portal Build a hosted customer portal flow with launch sessions, OTP/magic-link auth, and customer-scoped access. *** title: Customer portal description: Build a hosted customer portal flow with launch sessions, OTP/magic-link auth, and customer-scoped access. ----------------------------------------------------------------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; lomi. customer portal provides a hosted customer account area at `customers.lomi.africa` where end-users can: * View payment history and open invoice receipts when available * View and manage subscriptions (pause, resume, cancel at period end, uncancel) * Add, remove, and set default saved cards (when enabled in portal policy) * Retry failed subscription payments in-portal before checkout fallback * Download digital purchases from the library This guide describes the recommended production integration. ## Architecture (recommended) 1. Your merchant backend creates a one-time portal launch session. 2. You redirect/open the returned `launch_url`. 3. The hosted portal consumes the token once and asks the customer to verify via: * Email magic link, or * SMS OTP 4. Portal session is established and scoped to `(organization_id, customer_id, environment)`. 5. Customer sees only their own records. Do not mint launch sessions in browser code. Create them from your backend only. ## Create a launch session Use `POST /customers/{id}/portal`: * Optional `return_url` * Optional `flow_type` (`portal_home`, `subscription_cancel`, `subscription_manage`) * Optional `flow_subscription_id` (required for `subscription_cancel`) * Optional `flow_after_completion_url` See endpoint details and payload examples in [Customers](/api/customers/CustomersController_createPortalSession). ## Eligibility model A customer is eligible for portal access only if they: 1. Belong to your organization in the requested environment, and 2. Have at least one billing record (transaction or subscription) This avoids exposing an empty portal to contacts that never transacted. ## End-to-end example ```typescript import axios from "axios"; const apiKey = process.env.LOMI_SECRET_KEY!; const customerId = "2d8f4f8b-1ea8-4de9-9fd8-f52f743bb265"; const { data } = await axios.post( `https://api.lomi.africa/customers/${customerId}/portal`, { return_url: "https://merchant.example.com/account", flow_type: "subscription_cancel", flow_subscription_id: "3d6236f9-2c3e-4f0c-b00d-e2d73d3f0d24", flow_after_completion_url: "https://merchant.example.com/account/subscription-cancelled", }, { headers: { "X-API-KEY": apiKey, "Content-Type": "application/json", }, }, ); // Redirect customer to hosted portal return data.launch_url; ``` ```bash curl -sS -X POST "https://api.lomi.africa/customers/2d8f4f8b-1ea8-4de9-9fd8-f52f743bb265/portal" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "return_url": "https://merchant.example.com/account", "flow_type": "subscription_cancel", "flow_subscription_id": "3d6236f9-2c3e-4f0c-b00d-e2d73d3f0d24", "flow_after_completion_url": "https://merchant.example.com/account/subscription-cancelled" }' ``` ## Navigate customers to the portal There are three ways customers can reach the portal: ### 1. Default org URL (self-serve sign-in) ``` https://customers.lomi.africa/o/{your-org-slug} ``` Customers enter the email or phone used at checkout. No merchant API call required. Copy this URL from **Settings → Checkout → Storefront → Customer portal**. ### 2. Pre-authenticated launch (merchant app) When the customer is already identified in your app, create a launch session server-side and redirect to `launch_url`. If the session includes a `customer_id`, the portal **skips the second OTP** (trusted launch). ```typescript import { CustomersService } from "@lomi/sdk"; const { launch_url } = await CustomersService.createPortalLaunchSession(customerId, { return_url: "https://your-app.com/account", }); redirect(launch_url); ``` Next.js example (`app/billing/route.ts`): ```typescript import { redirect } from "next/navigation"; import { CustomersService } from "@lomi/sdk"; export async function GET() { const customerId = await getLoggedInCustomerId(); const { launch_url } = await CustomersService.createPortalLaunchSession(customerId, {}); redirect(launch_url); } ``` ### 3. Transactional emails Customer receipt emails include your org portal URL (`customers.lomi.africa/o/{slug}`) so buyers can return from their inbox. ## Headless API (optional) For custom portal UIs, use the portal session bearer token from a trusted launch or OTP flow: * `GET /customer-portal/me` * `GET /customer-portal/transactions` * `GET /customer-portal/subscriptions` * `POST /customer-portal/subscriptions/{id}/actions` * `GET /customer-portal/payment-methods` * `POST /customer-portal/payment-methods/setup-intent` * `POST /customer-portal/payment-methods/{id}/default` * `DELETE /customer-portal/payment-methods/{id}` * `POST /customer-portal/subscriptions/{id}/retry-payment` Pass `Authorization: Bearer `. ### Payment methods When `allow_payment_method_update` is enabled in portal policy (default: on), customers can: 1. Create a card setup intent via `POST /customer-portal/payment-methods/setup-intent` 2. Confirm the setup intent client-side with your card payment SDK 3. Persist the card with your attach flow (hosted portal calls the attach edge function after confirmation) Use `POST /customer-portal/payment-methods/{id}/default` to set the default card and sync active subscriptions. Use `DELETE /customer-portal/payment-methods/{id}` to remove a card (blocked when it is the sole card on a billable subscription). ### Failed payment recovery For `past_due` card subscriptions, `POST /customer-portal/subscriptions/{id}/retry-payment` attempts an off-session charge with the default saved card. On success, renewal is recorded and dunning is cleared. On failure, the response may include a `checkout_url` fallback (same path as automated renewal dunning). The hosted portal also exposes **Retry payment** alongside **Pay now** on subscription actions. ## Dashboard integration From **Settings → Checkout → Storefront**, configure the customer portal: * Allow pause / resume / cancel * Email magic link and SMS OTP sign-in * **Payment method updates**: allow customers to add, remove, and set default cards * **Return URL allowlist**: required when using `return_url` or `flow_after_completion_url` from your backend From **Customers**, open a customer with billing history and click **Open customer portal** to launch the hosted flow in a new tab (same as the API launch session). ## Subscription cancellation Customer-initiated cancellation is **at period end** by default: access continues until `next_billing_date`, then the subscription moves to `cancelled`. Customers can **uncancel** before that date from the portal. Schedule `finalize_cancel_at_period_end_subscriptions()` daily (e.g. pg\_cron) so period-end cancellations are applied automatically. ## Security controls * Launch tokens are one-time and short-lived (15 min) * Tokens are hashed at rest * OTP/magic challenges are hashed, expiring, and attempt-limited * Portal sessions are hashed, revocable, and renewed on activity * Requests are organization/customer scoped at SQL function level * Audit events recorded for launch, challenge, session, and subscription actions ## Production setup **Required** for the hosted portal app (`apps/customers`): * `CUSTOMER_PORTAL_COOKIE_SECRET`, seals handoff and flow cookies (min 16 characters) * `NEXT_PUBLIC_SUPABASE_URL` and `SUPABASE_SECRET_KEY`, RPC access and magic-link email **Optional**: * `CUSTOMER_PORTAL_SMS_WEBHOOK_URL`, POST `{ to, body }` to your SMS gateway. Without it, OTPs are only logged in non-production. Launch URLs and magic links default to `https://customers.lomi.africa` and the request origin respectively. No extra base-URL env vars are required for production. Also: * Ensure merchant backend uses server-side API key storage. * Add merchant return URLs to the portal allowlist before using `return_url` in API calls. * Schedule `finalize_cancel_at_period_end_subscriptions()` daily (included in `run_to_prod.sql` cron). * Monitor launch/session/challenge failures and abuse rates. * Keep customer phone/email normalization consistent in your CRM. ## Troubleshooting * **404 customer not found**: wrong customer ID or organization mismatch. * **400 invalid flow**: unsupported `flow_type` or missing `flow_subscription_id`. * **Launch URL opens expired page**: token reused or expired. * **Customer cannot proceed after contact input**: no eligible billing records. # 客户门户 Source: https://docs.lomi.africa/build/billing/customer-portal.zh 托管客户门户:启动会话、OTP 验证与客户范围访问。 *** title: 客户门户 description: 托管客户门户:启动会话、OTP 验证与客户范围访问。 --------------------------------------- 完整集成说明请参阅英文文档:[Customer portal (EN)](/build/billing/customer-portal)。 要点: * 默认入口:`https://customers.lomi.africa/o/{组织 slug}` * 预认证启动:`POST /customers/{id}/portal` 返回 `launch_url`(含 `customer_id` 时跳过二次 OTP) * 交易收据邮件包含组织门户链接 # Discount coupons Source: https://docs.lomi.africa/build/billing/discount-coupons Create, validate, stack, and track coupon discounts with the same rules used in checkout. *** title: Discount coupons description: Create, validate, stack, and track coupon discounts with the same rules used in checkout. ------------------------------------------------------------------------------------------------------ import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; The Discount coupons API supports strict server-side validation and usage tracking for checkout flows. This page describes coupon validation and application rules used by checkout and the API, including: * creation-time validation * checkout-time validation (customer, scope, quantity, frequency) * single and multi-coupon application * deduped coupon usage reservation and transaction linking ## How coupon logic works (end-to-end) At a high level, coupon handling follows this flow: 1. Create a coupon via **`POST /coupons`** with your constraints. 2. Validate at checkout (server-side rules for customer, scope, quantity, and frequency). 3. Compute and apply the discount on the checkout session, including stacked coupons when allowed. 4. Reserve usage against the checkout session until payment completes. 5. Link the reservation to the final transaction when payment succeeds. 6. Increment usage counters when the transaction reaches **`completed`**. This keeps coupon behavior deterministic between dashboard preview, hosted checkout, and API integrations. ## Create a discount coupon ### Request Body | Field | Type | Required | Description | | ----------------------- | --------- | ------------- | ----------------------------------------------------------- | | `code` | `string` | **Yes** | Unique coupon code (auto-uppercased) | | `discount_type` | `string` | No | `percentage` or `fixed` (default: `percentage`) | | `discount_percentage` | `number` | If percentage | Discount percentage (`> 0` and `<= 100`) | | `discount_fixed_amount` | `number` | If fixed | Fixed discount amount | | `description` | `string` | No | Coupon description | | `is_active` | `boolean` | No | Active status (default: `true`) | | `max_uses` | `number` | No | Maximum total uses | | `max_quantity_per_use` | `number` | No | Max quantity per use | | `valid_from` | `string` | No | Start date (ISO 8601) | | `expires_at` | `string` | No | Expiration date (ISO 8601) | | `customer_type` | `string` | No | `all`, `new`, `returning` | | `usage_frequency_limit` | `string` | No | `total`, `per_customer`, `per_day`, `per_week`, `per_month` | | `usage_limit_value` | `number` | Conditionally | Required if `usage_frequency_limit != total` | | `scope_type` | `string` | No | `organization_wide`, `specific_products`, `specific_prices` | | `product_ids` | `array` | No | Product IDs (if scope is specific) | ### Creation-time validation rules The API enforces these rules at creation: * coupon code is unique per organization * percentage and fixed discounts are mutually exclusive * `valid_from < expires_at` when both exist * `usage_limit_value` is required for non-`total` frequency modes * for `specific_products` / `specific_prices`, linked products must belong to the same organization ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live', }); // Percentage discount const coupon = await lomi.coupons.create({ code: 'SAVE20', discount_type: 'percentage', discount_percentage: 20, description: '20% off all products', max_uses: 100, expires_at: '2024-12-31T23:59:59Z', }); // Fixed amount discount const fixedCoupon = await lomi.coupons.create({ code: 'FLAT5000', discount_type: 'fixed', discount_fixed_amount: 5000, description: '5000 XOF off', customer_type: 'new', }); console.log(`Coupon created: ${coupon.code}`); ``` ```python from lomi import LomiClient import os client = LomiClient( api_key=os.environ["LOMI_SECRET_KEY"], environment="test" ) coupon = client.discount_coupons.create({ "code": "SAVE20", "discount_type": "percentage", "discount_percentage": 20, "description": "20% off all products", "max_uses": 100, "expires_at": "2024-12-31T23:59:59Z" }) print(f"Coupon created: {coupon['code']}") ``` ```bash curl -X POST "https://api.lomi.africa/coupons" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "SAVE20", "discount_type": "percentage", "discount_percentage": 20, "description": "20% off all products", "max_uses": 100, "expires_at": "2024-12-31T23:59:59Z" }' ``` *** ## Checkout validation behavior Checkout validation combines structural and business checks. A coupon must pass all checks below: 1. **Exists and active** in the same organization. 2. **Time window**: `valid_from` has started and `expires_at` has not passed. 3. **Global cap**: `current_uses < max_uses` (if `max_uses` set). 4. **Frequency cap** for the customer (when enabled): per customer/day/week/month. 5. **Quantity cap**: checkout quantity must not exceed `max_quantity_per_use`. 6. **Customer-type eligibility**: * `new`: customer must have no completed payment/installment transactions in org * `returning`: customer must have at least one completed payment/installment transaction * `all`: no customer-history restriction 7. **Scope check**: * `organization_wide`: valid for all products * `specific_products` / `specific_prices`: product must be linked in `coupon_product_links` If any check fails, the API returns a specific reason message. ## Discount calculation behavior `calculate_coupon_discount` computes discount on the base amount excluding optional fees: * `base_price = p_base_amount - p_fees_amount` * percentage coupon: `discount = base_price * percentage` * fixed coupon: `discount = fixed_amount` * discount is multiplied by quantity when applicable * discount is clamped so it cannot exceed the eligible base price * final amount is recomputed as `(base_price - discount) + fees` This protects against negative totals and over-discounting. ## Multi-coupon stacking (sequential) For multi-coupon flows, coupons are applied in order using a running amount: * coupon A applies to original current amount * coupon B applies to the reduced amount after A * and so on This is **sequential stacking**, not parallel summing.\ The response includes a breakdown per coupon with: * original amount before this coupon * discount amount for this coupon * final amount after this coupon If any coupon in the array fails validation, the whole multi-coupon calculation fails. ## Usage tracking and deduplication Coupon usage is recorded in `coupon_usage` with safeguards to prevent duplicate pending reservations: * a pending reservation is unique per `(coupon_id, checkout_session_id)` while `transaction_id IS NULL` * stale duplicates are cleaned before enforcing the partial unique index * when payment completes, reservation is linked to transaction (instead of inserting duplicates) * if no reservation exists, a new transaction-linked record is inserted This is the expected behavior for provider retries and webhook races. ## List discount coupons Retrieve all discount coupons for your organization. ```typescript const coupons = await lomi.coupons.list(); ``` ```python coupons = client.discount_coupons.list() ``` ```bash curl -X GET "https://api.lomi.africa/coupons" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Get a discount coupon Retrieve details of a specific coupon. ```typescript const coupon = await lomi.coupons.get('dc_abc123...'); ``` ```python coupon = client.discount_coupons.get('dc_abc123...') ``` ```bash curl -X GET "https://api.lomi.africa/coupons/dc_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Get coupon performance Retrieve usage statistics and revenue impact for a coupon (`completed` transactions only). ```typescript const performance = await lomi.coupons.getPerformance('dc_abc123...'); console.log(`Total uses: ${performance.total_uses}`); console.log(`Total discounted: ${performance.total_discount_amount}`); console.log(`Revenue generated: ${performance.total_revenue}`); console.log(`Avg order value: ${performance.average_order_value}`); ``` ```python performance = client.discount_coupons.get_performance('dc_abc123...') print(f"Total uses: {performance['total_uses']}") ``` ```bash curl -X GET "https://api.lomi.africa/coupons/dc_abc123.../performance" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ### Response ```json { "total_uses": 45, "total_discounts": 25000, "total_revenue": 150000, "average_discount": 555.56, "unique_customers": 38 } ``` *** ## Discount Coupon Object | Field | Type | Description | | ------------------------------ | --------- | ----------------------------------------------------------- | | `id` | `string` | Unique identifier | | `code` | `string` | Coupon code | | `discount_type` | `string` | `percentage` or `fixed` | | `discount_percentage` | `number` | Percentage value | | `discount_fixed_amount` | `number` | Fixed amount value | | `customer_type` | `string` | `all`, `new`, `returning` | | `usage_frequency_limit` | `string` | `total`, `per_customer`, `per_day`, `per_week`, `per_month` | | `usage_limit_value` | `number` | Frequency limit count where applicable | | `is_active` | `boolean` | Active status | | `max_uses` | `number` | Maximum uses | | `current_uses` | `number` | Current use count | | `max_quantity_per_use` | `number` | Max quantity allowed per redemption | | `valid_from` | `string` | Start date | | `expires_at` | `string` | Expiration date | | `scope_type` | `string` | Application scope | | `product_links` | `array` | Linked products where scope is specific | | `completed_redemptions` | `number` | Completed redemptions count | | `distinct_customers_completed` | `number` | Distinct customers with completed redemptions | | `created_at` | `string` | Creation timestamp | *** ## Common implementation patterns ### Validate before applying For a clean UX: 1. validate coupon first (frontend validation endpoint), 2. show discount preview, 3. apply coupon during checkout creation/confirmation. ### Handle retries safely Payment providers may retry callbacks. The coupon reservation/linking logic is designed to be idempotent around checkout session + coupon pairs. ### Prefer customer context when possible For `new` and `returning` restricted coupons, send `customer_id` so eligibility can be evaluated correctly. *** ## Error Responses | Status | Description | | ------ | ------------------------------- | | `400` | Invalid input or duplicate code | | `401` | Invalid or missing API key | | `404` | Coupon not found | ## Worked examples ### New-customer-only coupon Coupon configuration: * `customer_type = new` * `scope_type = organization_wide` * `discount_type = percentage` * `discount_percentage = 20` Behavior: * Customer with no completed payment/installment transactions in the organization: coupon is valid. * Customer with at least one completed payment/installment transaction: coupon is rejected as not eligible. * If customer context is missing, eligibility checks for restricted customer types may fail. ### Returning-customer-only coupon Coupon configuration: * `customer_type = returning` * `usage_frequency_limit = per_month` * `usage_limit_value = 1` Behavior: * Returning customer can redeem once per month. * Second redemption in the same month is rejected. * Redemptions in a new month are allowed again (subject to other limits). ### Product-scoped coupon Coupon configuration: * `scope_type = specific_products` * `product_ids = [Product A, Product B]` Behavior: * Checkout for Product A or B: can pass scope checks. * Checkout for Product C: rejected (`coupon is not applicable to this product`). * Organization-wide coupons skip product-link checks. ### Quantity-capped coupon Coupon configuration: * `max_quantity_per_use = 2` Behavior: * Quantity `1` or `2`: valid. * Quantity `3+`: rejected with a quantity-limit message. ### Fixed discount clamped to base amount Checkout: * Base amount: `3,000` * Fees amount: `500` * Eligible base price: `2,500` Coupon: * `discount_type = fixed` * `discount_fixed_amount = 5,000` Result: * Discount is clamped to `2,500` (cannot exceed eligible base). * Final amount is `(2,500 - 2,500) + 500 = 500`. ### Sequential stacking (two coupons) Checkout amount: `10,000` Coupons in order: 1. `SAVE20` (20%) 2. `FLAT1000` (fixed 1,000) Sequential calculation: * After `SAVE20`: discount `2,000`, running amount `8,000` * After `FLAT1000`: discount `1,000`, running amount `7,000` * Total discount: `3,000` Important: second coupon is applied to the reduced running amount, not the original amount. ### Pending reservation and transaction linking Flow: 1. Coupon is applied to checkout session. 2. A pending `coupon_usage` reservation is recorded for `(coupon_id, checkout_session_id)`. 3. Payment provider callback creates/finalizes transaction. 4. Reservation is linked to the transaction. Why this matters: * Prevents duplicate coupon usage rows during retries/webhook races. * Keeps reporting accurate when providers retry callbacks. ### Safe retry behavior If the same provider event is retried: * existing pending reservation is updated/linked when possible, * duplicate inserts are prevented by uniqueness and conflict handling, * usage counting remains consistent when transaction completion logic runs. ### Stacking order changes the total discount Same two coupons as sequential stacking, but reverse the order: 1. `FLAT1000` first: discount `1,000`, running amount `9,000` 2. `SAVE20` second: 20% of `9,000` = `1,800`, running amount `7,200` 3. **Total discount: `2,800`** (not `3,000`) Always show the applied order in UI and persist the same order server-side. ### Usage frequency: sliding window vs calendar Depending on which validation path runs, usage limits may be evaluated differently: * One path counts coupon usage rows in a **sliding** window (for example last 24 hours for `per_day`). * Another path ties `per_day` / `per_week` / `per_month` to **calendar** boundaries. For a coupon limited to **one use per day**, a customer who redeems at 23:59 may or may not redeem again at 00:01 the next day, depending on which helper your checkout stack calls. **Treat limits as “at most N redemptions in the configured period”** and test in sandbox. ### Re-applying a coupon on the same checkout session When a coupon is applied to a session that already has a pending `coupon_usage` row, the system may **upsert** that row (update discount and timestamps) instead of inserting duplicates, as long as the transaction is not finalized yet. Implications: * Changing the coupon code or recomputing the cart should **replace** the pending reservation, not stack duplicate rows. * After **payment completes**, usage is keyed off the transaction; do not rely on session-only rows for accounting. ### Free or fully discounted checkouts When the discount brings the payable amount to **zero** (for example 100% off campaigns), the platform may record a **free** completion path using dedicated provider/method codes so ledger and webhooks remain consistent. Merchant-facing reporting should still show **list price**, **discount**, and **net**. ### Client-side retries Your app retries `POST /checkout-sessions` or `apply coupon` due to a flaky network: * Use the **same idempotency key** or session id so you do not create parallel sessions. * On the server, duplicate completion webhooks should not double-increment usage if transaction completion is idempotent, still avoid duplicate **client-initiated** applies when possible. ### Integration notes * Always validate before final apply. * Send customer context when using `new`/`returning` restricted coupons. * Keep coupon application idempotent in your own client retries. * For multi-coupon UX, preserve intended coupon order. * Document for your team whether **percentage or fixed** coupons should be applied first (product decision, affects totals). # Billing Source: https://docs.lomi.africa/build/billing Products, subscriptions, usage billing, customer portal, and coupons. *** title: Billing description: Products, subscriptions, usage billing, customer portal, and coupons. index: true ----------- Define what you sell, then bill it monthly, by usage, or with a discount. The customer portal is for self-serve plan changes after signup. # Products Source: https://docs.lomi.africa/build/billing/products Manage products, prices, and subscription configurations. *** title: Products description: Manage products, prices, and subscription configurations. ---------------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; The Products API allows you to manage your product catalog. Products represent items or services you sell, and each product can have up to **3 active prices**. ## Create a product Create a product with one or more prices. At least one price is required. **API reference (full request schema):** [Create product](/api/products/ProductsController_create) **Essentials:** * `name`, `product_type` (`one_time`, `recurring`, `usage_based`), and a `prices` array are required. * Each price needs `amount` and `currency_code`. Recurring products also need `billing_interval` on the price. * Recurring-only fields: `trial_enabled`, `trial_period_days`, `failed_payment_action`, `charge_day`, `first_payment_type`: see the API page. * Usage-based products: `usage_aggregation`, `usage_unit`: see the API page. **Optional metadata** for recurring products (via `metadata` on create/update): `subscription_length` (`"automatic"` by default) and `fixed_charges` (integer number of billing cycles before expiry). These are not top-level API fields; store them in `metadata`. **Pay what you want** (`pricing_model: pay_what_you_want`, one-time only): `minimum_amount` is required; `amount` is the suggested unit price. The server enforces `amount >= minimum_amount` and optional `maximum_amount`. Full price fields: [Create product](/api/products/ProductsController_create). ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live', }); // One-time product const product = await lomi.products.create({ name: 'E-book Bundle', description: 'Complete guide collection', product_type: 'one_time', prices: [ { amount: 25000, currency_code: 'XOF', is_default: true, }, ], display_on_storefront: true, }); // Subscription product with trial const subscription = await lomi.products.create({ name: 'Premium Plan', description: 'Monthly premium access', product_type: 'recurring', prices: [ { amount: 10000, currency_code: 'XOF', billing_interval: 'month', is_default: true, }, ], trial_enabled: true, trial_period_days: 14, failed_payment_action: 'continue', }); // Pay what you want (one-time only) const tipJar = await lomi.products.create({ name: 'Community tip jar', product_type: 'one_time', prices: [ { pricing_model: 'pay_what_you_want', amount: 1000, minimum_amount: 500, maximum_amount: 5000, currency_code: 'XOF', is_default: true, }, ], }); console.log(`Product created: ${product.product_id}`); ``` ```python from lomi import LomiClient import os client = LomiClient( api_key=os.environ["LOMI_SECRET_KEY"], environment="test" ) # One-time product product = client.products.create({ "name": "E-book Bundle", "description": "Complete guide collection", "product_type": "one_time", "prices": [ { "amount": 25000, "currency_code": "XOF", "is_default": True } ], "display_on_storefront": True }) # Pay what you want (one-time only) tip_jar = client.products.create({ "name": "Community tip jar", "product_type": "one_time", "prices": [ { "pricing_model": "pay_what_you_want", "amount": 1000, "minimum_amount": 500, "maximum_amount": 5000, "currency_code": "XOF", "is_default": True } ] }) print(f"Product created: {product['product_id']}") ``` ```bash curl -X POST "https://api.lomi.africa/products" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "E-book Bundle", "description": "Complete guide collection", "product_type": "one_time", "prices": [ { "amount": 25000, "currency_code": "XOF", "is_default": true } ], "display_on_storefront": true }' # Pay what you want (one-time only) curl -X POST "https://api.lomi.africa/products" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Community tip jar", "product_type": "one_time", "prices": [ { "pricing_model": "pay_what_you_want", "amount": 1000, "minimum_amount": 500, "maximum_amount": 5000, "currency_code": "XOF", "is_default": true } ] }' ``` *** ## List products **API reference:** [List products](/api/products/ProductsController_findAll), supports `isActive`, `limit`, and `offset`. ```typescript const products = await lomi.products.list({ isActive: true, limit: 20, }); ``` ```python products = client.products.list(isActive=True, limit=20) ``` ```bash curl -X GET "https://api.lomi.africa/products?isActive=true&limit=20" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Get a product **API reference:** [Get product](/api/products/ProductsController_findOne), returns the product with all prices. ```typescript const product = await lomi.products.get('prod_abc123...'); console.log(`Default price: ${product.prices.find(p => p.is_default)?.amount}`); ``` ```python product = client.products.get('prod_abc123...') ``` ```bash curl -X GET "https://api.lomi.africa/products/prod_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Add a price to a product **API reference:** [Add price](/api/products/ProductsController_addPrice) Products can have a maximum of **3 active prices**. You cannot modify existing prices, create a new one instead. ```typescript const price = await lomi.products.addPrice('prod_abc123...', { amount: 50000, currency_code: 'XOF', billing_interval: 'year', }); console.log(`New price added: ${price.price_id}`); // Pay what you want price const pwywPrice = await lomi.products.addPrice('prod_abc123...', { pricing_model: 'pay_what_you_want', amount: 1000, minimum_amount: 500, maximum_amount: 5000, currency_code: 'XOF', }); ``` ```python price = client.products.add_price('prod_abc123...', { "amount": 50000, "currency_code": "XOF", "billing_interval": "year" }) ``` ```bash curl -X POST "https://api.lomi.africa/products/prod_abc123.../prices" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 50000, "currency_code": "XOF", "billing_interval": "year" }' ``` *** ## Set default price **API reference:** [Set default price](/api/products/ProductsController_setDefaultPrice) ```typescript const product = await lomi.products.setDefaultPrice('prod_abc123...', 'price_def456...'); ``` ```python product = client.products.set_default_price('prod_abc123...', 'price_def456...') ``` ```bash curl -X POST "https://api.lomi.africa/products/prod_abc123.../prices/price_def456.../default" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Related API * [Create product](/api/products/ProductsController_create) * [List products](/api/products/ProductsController_findAll) * [Get product](/api/products/ProductsController_findOne) * [Add price](/api/products/ProductsController_addPrice) * [Set default price](/api/products/ProductsController_setDefaultPrice) * [Checkout with a product](/build/accept/checkout) # Subscriptions Source: https://docs.lomi.africa/build/billing/subscriptions Manage recurring customer subscriptions. *** title: Subscriptions description: Manage recurring customer subscriptions. ----------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; The Subscriptions API lists and retrieves **subscription instances** (a customer on a recurring plan). These are typically created when customers buy recurring products through Checkout sessions or Payment links. SDKs expose **`/subscriptions`** helpers for listing, retrieving, updating, and canceling subscriptions. For route-specific examples and status transitions, see **[Subscriptions](/api/subscriptions)**. Immutable fields and cancel-only rules follow the OpenAPI descriptions for each operation. For signup flows (trials, first payment types, checkout session amounts), see **[Checkout behavior: Subscription checkout](/build/accept/checkout-behavior#subscription-checkout)**. ## How subscriptions are created Subscription instances are created when a customer buys a **recurring product** through: * [Hosted checkout](/build/accept/checkout) or [payment links](/build/accept/payment-links) with a recurring `product_id` * Your storefront subscribe flow There is no standalone "create subscription" API for signup, the checkout or link flow creates the instance after the first successful payment (or trial rules on the product). Monitor **webhook events** to track payment confirmations for each billing cycle. Listen for `SUBSCRIPTION_RENEWED` and renewal-related `PAYMENT_FAILED` events, see [Checkout behavior: Subscription checkout](/build/accept/checkout-behavior#subscription-checkout). ## Renewals and failed payments lomi. uses **two renewal paths**, depending on how the customer pays: ### Card subscriptions (automatic) When signup saved a card payment method (`provider_customer_id` + `provider_payment_method_id`), renewals run **off-session** on `next_billing_date`. A daily job charges the saved card and records the renewal transaction. * Webhook: **`SUBSCRIPTION_RENEWED`** on success * Webhook: **`PAYMENT_FAILED`** on renewal failure (with processor retry/dunning where configured) ### Wave / MTN subscriptions (manual renewal checkout) Mobile money does not expose a saved off-session token like cards. When no card payment method exists, lomi. **does not auto-debit** the wallet. Instead: 1. **Notification crons** email or message a **hosted renewal checkout link** before `next_billing_date`. 2. The customer pays on that link; webhooks confirm the cycle. 3. If the customer does not pay in time, **overdue processing** may set the subscription to `past_due`, `paused`, or `cancelled` per the product's `failed_payment_action`. This matches how async mobile-money rails work in production (PIN approval, no standing mandate). It is intentional, not a missing card feature. **Support shortcut:** "Why didn't my Wave subscription auto-charge?" → Mobile money renewals require the customer to open the renewal checkout link and approve payment. Cards renew automatically when a payment method was saved at signup. Your integration should: 1. **Listen for `SUBSCRIPTION_RENEWED`**: successful billing cycle; extend access or send a receipt. 2. **Listen for `PAYMENT_FAILED`** on renewal transactions, customer may need to pay the renewal checkout link or update a card in the [customer portal](/build/billing/customer-portal). 3. **Poll or list subscriptions** via `GET /subscriptions` (optionally with `customer_id` or `status` filters) when webhooks are delayed; use `status` and `next_billing_date` from the API response only. Cancel or pause via `POST /subscriptions/{id}/cancel` or `PATCH /subscriptions/{id}` as documented in the [Subscriptions API](/api/subscriptions). For **metered / usage-based** products (API calls, credits, seats), see **[Usage billing](/build/billing/usage-billing)**-usage subscriptions and meters are separate from recurring checkout subscriptions. ## List subscriptions **API reference:** [List subscriptions](/api/subscriptions/SubscriptionsController_findAll), supports `page` and `pageSize`. ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live', }); const subscriptions = await lomi.subscriptions.list({ page: 1, pageSize: 20, }); subscriptions.forEach(sub => { console.log(`${sub.id}: ${sub.status} - ${sub.amount} ${sub.currency_code}`); }); ``` ```python from lomi import LomiClient import os client = LomiClient( api_key=os.environ["LOMI_SECRET_KEY"], environment="test" ) subscriptions = client.subscriptions.list(page=1, pageSize=20) for sub in subscriptions: print(f"{sub['id']}: {sub['status']}") ``` ```bash curl -X GET "https://api.lomi.africa/subscriptions?page=1&pageSize=20" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Get subscriptions for a customer **API reference:** [Subscriptions by customer](/api/subscriptions/CustomersController_getSubscriptions) ```typescript const customerSubs = await lomi.customers.getSubscriptions('cus_abc123...'); ``` ```python customer_subs = client.subscriptions.get_by_customer('cus_abc123...') ``` ```bash curl -X GET "https://api.lomi.africa/subscriptions/customer/cus_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Get a subscription **API reference:** [Get subscription](/api/subscriptions/SubscriptionsController_findOne) ```typescript const subscription = await lomi.subscriptions.get('sub_abc123...'); console.log(`Status: ${subscription.status}`); console.log(`Next billing: ${subscription.current_period_end}`); ``` ```python subscription = client.subscriptions.get('sub_abc123...') print(f"Status: {subscription['status']}") ``` ```bash curl -X GET "https://api.lomi.africa/subscriptions/sub_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Subscription lifecycle Subscriptions are created when a customer completes checkout for a **recurring** product (hosted checkout, payment link, storefront subscribe flow, or API session with `product_type: recurring`). Typical status flow: | Status | Meaning | | ----------- | ------------------------------------------------------------------------------- | | `pending` | Created but not yet active (rare at signup). | | `trial` | Free trial in progress; no charge until trial ends. | | `active` | Paid and renewing on schedule. | | `past_due` | A renewal payment failed; retries or merchant action may apply. | | `paused` | Billing paused (e.g. after failed payment when product is configured to pause). | | `cancelled` | Cancelled by merchant or customer. | | `expired` | Fixed-term subscription ended or trial converted without payment method. | **First payment** behavior is controlled on the product via `first_payment_type`: | Value | First checkout charge | | ------------- | --------------------------------------------------------------------------- | | `initial` | Full recurring price (or prorated amount if combined with proration rules). | | `non_initial` | **$0** at signup; first charge on the first billing date. | | `prorated` | Partial amount for the remainder of the current billing period. | **Trials** (`trial_enabled` + `trial_period_days`): signup charge is **$0**. the card processor saves the card for later billing; mobile money can complete signup without an immediate charge. After the trial, status moves to `active` and billing begins on `next_billing_date`. **Renewals** run automatically for saved card payment methods. If no method is on file (common for mobile money), lomi. may fall back to a **manual renewal checkout link** for the customer. *** ## Merchant-owned billing UI If customers manage subscriptions in **your** app (not lomi. customer portal): 1. Call lomi. APIs when they cancel, pause, upgrade, or resume (`POST /subscriptions/{id}/cancel`, `PATCH /subscriptions/{id}`, etc.). 2. Listen for **`SUBSCRIPTION_UPDATED`** and **`SUBSCRIPTION_CANCELLED`** webhooks (same events whether the change came from your API, the portal, or cron). 3. Reconcile entitlements with `GET /subscriptions/{id}` when you need authoritative state. 4. For failed renewals, use the [customer portal retry payment](/build/billing/customer-portal#failed-payment-recovery) headless endpoint or redirect customers to the hosted portal billing/subscriptions pages. lomi. is the billing source of truth; your app should not cancel only in your database without calling the API. *** ## Cancel a subscription **API reference:** [Cancel subscription](/api/subscriptions/SubscriptionsController_cancel) Pass `cancel_at_period_end: true` to cancel at the end of the billing period; omit or set `false` to cancel immediately. Optional `cancellation_reason` for your records. ```typescript // Cancel immediately const cancelled = await lomi.subscriptions.cancel('sub_abc123...'); // Cancel at period end const scheduledCancel = await lomi.subscriptions.cancel('sub_abc123...', { cancel_at_period_end: true, cancellation_reason: 'Customer requested cancellation', }); console.log(`Subscription will cancel at: ${scheduledCancel.cancel_at}`); ``` ```python # Cancel immediately cancelled = client.subscriptions.cancel('sub_abc123...') # Cancel at period end scheduled_cancel = client.subscriptions.cancel('sub_abc123...', { "cancel_at_period_end": True, "cancellation_reason": "Customer requested cancellation" }) ``` ```bash curl -X POST "https://api.lomi.africa/subscriptions/sub_abc123.../cancel" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "cancel_at_period_end": true, "cancellation_reason": "Customer requested cancellation" }' ``` *** ## Uncancel a scheduled cancellation **API reference:** [Uncancel subscription](/api/subscriptions/SubscriptionsController_resume) ```bash curl -X POST "https://api.lomi.africa/subscriptions/sub_abc123.../resume" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Change plan **API reference:** [Change subscription plan](/api/subscriptions/SubscriptionsController_changePlan) ```bash curl -X POST "https://api.lomi.africa/subscriptions/sub_abc123.../change-plan" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"price_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"}' ``` *** ## Webhooks Subscribe to these **SCREAMING\_SNAKE\_CASE** events (same as [Webhooks](/build/reliability)): | Event | Description | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `SUBSCRIPTION_CREATED` | New subscription after successful signup (including trial signup). | | `SUBSCRIPTION_UPDATED` | Subscription changed (pause, resume, plan change, cancel scheduled, uncancel, etc.). Payload includes `previous_attributes`. | | `SUBSCRIPTION_RENEWED` | A billing cycle renewed successfully. | | `SUBSCRIPTION_CANCELLED` | Subscription was cancelled or expired. | Renewal **payment failures** do not emit a separate subscription webhook. Listen for **`PAYMENT_FAILED`** on the failed renewal transaction, and monitor subscription `status` (`past_due`, `paused`, etc.) via the API. lomi. does not emit a webhook for trial card setup (`setup_intent.succeeded`). Listen for **`SUBSCRIPTION_CREATED`** when signup completes, not when the card form first loads. **Customer notifications:** Trial and $0 signups (no completed payment transaction) trigger a **subscription signup confirmation** email when `customer_notifications.subscription_signups.email` is enabled (default: on). Paid initial signups receive the standard transaction receipt instead. *** ## Related API * [List subscriptions](/api/subscriptions/SubscriptionsController_findAll) * [Get subscription](/api/subscriptions/SubscriptionsController_findOne) * [Subscriptions by customer](/api/subscriptions/CustomersController_getSubscriptions) * [Cancel subscription](/api/subscriptions/SubscriptionsController_cancel) * [Uncancel subscription](/api/subscriptions/SubscriptionsController_resume) * [Change plan](/api/subscriptions/SubscriptionsController_changePlan) * [Update subscription](/api/subscriptions/SubscriptionsController_update) * [Checkout behavior: subscriptions](/build/accept/checkout-behavior#subscription-checkout) * [Customer portal](/build/billing/customer-portal) # Usage billing Source: https://docs.lomi.africa/build/billing/usage-billing Meter usage, enroll customers on usage-based products, and read billing periods. *** title: Usage billing description: Meter usage, enroll customers on usage-based products, and read billing periods. --------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; Usage billing lets you charge customers based on **metered consumption** (API calls, seats, credits, etc.) on top of `usage_based` products. The flow is contract-first: every step below maps to a public REST endpoint in [openapi.json](https://github.com/lomiafrica/lomi./blob/main/apps/docs/openapi.json). For product setup (`product_type: usage_based`, prices, aggregation), see **[Products](/build/billing/products)**. Usage subscriptions are separate from recurring checkout subscriptions, use **`POST /usage/subscriptions`** to enroll customers on usage-based plans. ## What this is not There is no merchant-facing billing cycle id, and no API to change the amount before the bill date. Usage is ingested, then billed on the period. Create the product and price first, enroll with `POST /usage/subscriptions`, then send events. See [Products](/build/billing/products). ## Golden path ### 1. Create a usage-based product Create a product with `product_type: usage_based` and a recurring price. See [Products](/build/billing/products). ### 2. Create a meter Define the billable metric code your app will send on each usage event. ```bash curl -X POST "https://sandbox.api.lomi.africa/meters" \ -H "Authorization: Bearer $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "api_calls", "product_id": "prod_...", "filter": { "code": "api_calls" }, "aggregation": { "type": "sum", "property": "quantity" } }' ``` → [Create meter](/api/meters/MetersController_create) ### 3. Enroll the customer (usage subscription) Before ingesting usage for a customer, enroll them on the usage-based product. ```bash curl -X POST "https://sandbox.api.lomi.africa/usage/subscriptions" \ -H "Authorization: Bearer $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cus_...", "product_id": "prod_..." }' ``` → [Create usage subscription](/api/usage/events/UsageEventsController_createUsageSubscription) ### 4. Record usage events Send usage as it happens. Use a stable `transaction_id` per logical event for idempotency. ```bash curl -X POST "https://sandbox.api.lomi.africa/usage/events" \ -H "Authorization: Bearer $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "transaction_id": "evt_unique_123", "code": "api_calls", "customer_id": "cus_...", "subscription_id": "sub_...", "quantity": 1 }' ``` Returns **202 Accepted**; processing is asynchronous. → [Record usage event](/api/usage/events/UsageEventsController_ingest) ### 5. Read balances and billing periods | Goal | Endpoint | | ----------------------- | ----------------------------------------------------------------------------- | | Customer meter wallet | [Get meter balance](/api/meters/MetersController_getBalance) | | Usage in current period | [Get subscription usage](/api/subscriptions/SubscriptionsController_getUsage) | | Historical periods | [List billing periods](/api/usage/UsageBillingController_listPeriods) | | Prepaid credits | [Credit wallet](/api/usage/UsageBillingController_creditWallet) | | Feature gating | [Check entitlement](/api/usage/UsageBillingController_checkEntitlement) | ## Reconciliation * **List or get usage events** when webhooks are delayed: [List usage events](/api/usage/events/UsageEventsController_findAll), [Get usage event](/api/usage/events/UsageEventsController_findOne). * **Revenue reporting** across MRR + usage + one-time: [Combined revenue metrics](/api/usage/UsageBillingController_getRevenue). ## Reference: lomi. Radar metering [lomi. Radar](/build/money/radar) bills per screened charge when enabled on your organization. Each evaluation calls `enqueue_usage_event` with meter code **`radar_screen`** (aggregation: `count`). 1. Enable Radar via [PATCH /organizations/radar-settings](/api/organizations/OrganizationsController_updateRadarSettings) (`enabled: true`). A `radar_screen` meter is created automatically when a matching usage-based product exists. 2. Every screened charge (card, MTN, Wave) records one usage unit while Radar is on. 3. Read balances and periods with the same endpoints as other usage meters ([Get meter balance](/api/meters/MetersController_getBalance), [List billing periods](/api/usage/UsageBillingController_listPeriods)). ```bash curl -X POST "https://sandbox.api.lomi.africa/usage/events" \ -H "Authorization: Bearer $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "transaction_id": "radar_screen_evt_123", "code": "radar_screen", "customer_id": "cus_...", "quantity": 1, "properties": { "assessment_id": "ra_...", "rail": "card" } }' ``` Platform ingestion is automatic when Radar runs; the example shows the event shape for reconciliation. ## Related * [Radar](/build/money/radar): transaction screening product * [Subscriptions](/build/billing/subscriptions): recurring checkout subscriptions (distinct from usage subscriptions) * [Products](/build/billing/products): catalog and `usage_based` configuration * [Verify payments](/build/reliability/verify-payments): confirm transaction status before fulfilling access # Checkout behavior Source: https://docs.lomi.africa/build/accept/checkout-behavior Sessions, payment links, amounts, coupons, and expiry rules for hosted checkout. *** title: Checkout behavior description: Sessions, payment links, amounts, coupons, and expiry rules for hosted checkout. --------------------------------------------------------------------------------------------- Checkout combines **checkout sessions**, **payment links**, **products/prices**, **coupons**, and **provider** flows. This page explains cross-cutting rules; endpoint details remain in each resource page. ## Checkout sessions * Sessions are typically **time-bound** (default expiration window is documented on [Checkout sessions](/build/accept/checkout)). * Status progresses (e.g. open → completed/expired) as the customer pays or time runs out. * **`price_id`** on the session selects which product price applies; amount may be derived or validated against the product’s prices. ## Payment links * **Product** links resolve amount from catalog prices. When **`price_id`** is set, the initial checkout session uses that price row’s suggested `amount` (not only the product default). * **Instant** links use a fixed amount you supply. * Multi-line flows may set **`has_line_items`** in session metadata; totals aggregate line items, optional shipping/tax, and product-linked fees where configured. * **Pay-what-you-want products** must use a **single-product** link or session. Multi-item carts reject PWYW line items (`line_items_pwyw_not_supported`). ## Pay what you want Flexible pricing is available for **one-time** products only (`pricing_model: pay_what_you_want` on a price row): * **Per-unit pricing**: the customer chooses a unit price; total = unit × `quantity`. * **`amount` on the price**: suggested unit price pre-filled at checkout (defaults to `minimum_amount` if omitted when creating the product). * **Bounds**: `minimum_amount` (required) and optional `maximum_amount` cap what the customer may pay per unit. The API and database **reject** prices where suggested `amount` is outside `[minimum_amount, maximum_amount]`. * **Payment links**: product links pre-fill the suggested amount from the linked `price_id` (or default price); the hosted checkout page lets the buyer adjust within bounds before paying. * **API sessions**: when creating a checkout session with `product_id`/`price_id`, pass `amount` as the product subtotal or omit it to use the suggested price × quantity. See [Checkout sessions](/build/accept/checkout) and [Products](/build/billing/products). **Do not include pay-what-you-want prices in multi-item checkout.** Use a dedicated single-product payment link or checkout session for donations and flexible pricing. Cart APIs return `line_items_pwyw_not_supported`. ## Subscription checkout Recurring products (`product_type: recurring`) create a **subscription instance** when signup completes. This applies to hosted checkout, payment links, storefront subscribe flows, and API-created sessions with a recurring `product_id`. **Do not mix recurring and one-time products in a single cart checkout.** Multi-line `create_checkout_session_with_line_items` is intended for one-time products. Sell subscriptions via a dedicated session or payment link for the recurring product. ### First payment and trials Product fields control the **first checkout charge** (see [Products](/build/billing/products)): | Setting | Signup charge | | --------------------------------- | --------------------------------------------------------------- | | `first_payment_type: initial` | Charge the recurring price (or prorated portion). | | `first_payment_type: non_initial` | **$0** at signup; first charge on `next_billing_date`. | | `first_payment_type: prorated` | Partial charge for the rest of the current billing period. | | `trial_enabled: true` | **$0** for `trial_period_days`; billing starts after the trial. | Hosted checkout and storefront resolve the display total from server-side signup terms. The **checkout session `amount`** may still reflect the **catalog price** so session creation passes validation (`amount` must be > 0), while the customer is charged **$0** or only a card setup is collected for card trials. ### Payment methods at signup | Provider | Trial / $0 signup | | --------------- | ---------------------------------------------------------------------------------------------- | | **Cards** | SetupIntent saves the card; no charge until trial ends or first billing date. | | **Wave / MTN** | Signup completes without an immediate charge when `requires_payment` is false. | | **Paid signup** | Normal payment flow; first transaction type is `instalment` and links to the new subscription. | ### Renewals and failures lomi. uses different renewal mechanics by payment rail: | Rail | Renewal behavior | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Cards** | Off-session charge on the saved card payment method on `next_billing_date`. | | **Wave / MTN** | No saved off-session token. Before each cycle, lomi. sends a **manual renewal checkout link** (email/WhatsApp). The customer must approve payment on their phone. | **Why mobile money is manual:** Wave and MTN require per-payment customer approval. lomi. does not auto-debit wallets for subscription cycles without a card on file. * Failed renewals may set subscription status to `past_due` or `paused` depending on `failed_payment_action` on the product (`continue`, `pause`, `cancel`). * Overdue subscriptions that never receive payment may be failed by scheduled dunning after the grace window. * Listen for **`SUBSCRIPTION_RENEWED`** and **`PAYMENT_FAILED`** (renewal transactions), not a separate `subscription.payment_failed` event. See [Subscriptions: Renewals and failed payments](/build/billing/subscriptions#renewals-and-failed-payments) for the full model. ### Fixed-term subscriptions Optional product **metadata** keys (set via API `metadata` or dashboard): | Key | Description | | --------------------- | --------------------------------------------------------- | | `subscription_length` | `"automatic"` (default) or a fixed term strategy. | | `fixed_charges` | Number of billing cycles before the subscription expires. | See [Subscriptions](/build/billing/subscriptions) for lifecycle and webhook details. ## Coupons * Validation enforces **dates**, **usage limits**, **customer type** (new vs returning), **scope** (org-wide vs specific products), and **quantity** caps. * Multiple coupons (when supported) apply **sequentially** to a running amount, see [Discount coupons](/build/billing/discount-coupons) and [Coupon logic examples](/build/billing/discount-coupons). ## Cross-currency When session currency differs from a product price currency, server logic may **convert** for validation so the paid amount matches the configured price in another currency. ## Checkout form fields Control which customer fields appear on the hosted checkout form and whether they are required. Override per [payment link](/build/accept/payment-links) or set flags on each [checkout session](/build/accept/checkout). Organization defaults for **email**, **phone**, and **billing address** are configured in the dashboard under **Settings → Checkout**. **`require_name`** is available on payment links and checkout sessions via the API (defaults to required when unset). Each `require_*` flag is a boolean. When `true`, the field is **shown and required** before payment. When `false`, behavior depends on the field (see below). Passing `customer_name`, `customer_email`, or `customer_phone` on a session only **pre-fills** values; it does not change which fields are shown. ### System field flags | Field | API flag | Default when unset | When `true` | When `false` | | --------------- | ------------------------- | ------------------ | ----------------------------------------- | --------------------------------------------------------- | | Name | `require_name` | `true` | Name field shown and required | Name field hidden | | Email | `require_email` | `true` | Email field shown and required | Email field hidden | | Phone | `require_phone` | `false` | Phone field shown and required | See [Email and phone together](#email-and-phone-together) | | Billing address | `require_billing_address` | `false` | Billing address fields shown and required | Billing address hidden | ### `require_name` * **`true` (default):** The full name field is shown. The customer must enter a non-empty value to continue. * **`false`:** The name field is hidden on the form. Checkout still creates or updates the customer with a display name using the first available value: name (if provided elsewhere), email, phone, then `"Customer"`. Use `customer_name` on a checkout session to pre-fill the name when the field is visible. ### `require_email` * **`true` (default):** The email field is shown and required. The value must contain `@`. * **`false`:** The email field is hidden. Checkout does not ask for email even if `customer_email` was passed for pre-fill. Hiding email affects how `require_phone` is interpreted (see below). ### `require_phone` * **`true`:** The phone field is shown and required. The number must be valid for the selected country. * **`false` (default):** Behavior depends on whether email is visible: * If email is **visible**, phone is shown as **optional**. * If email is **hidden**, phone is **hidden** too (unless you set `require_phone: true`). Use `customer_phone` on a checkout session to pre-fill the phone when the field is visible. ### Email and phone together After flags are resolved, hosted checkout applies these rules: | `require_email` | `require_phone` | What the customer sees | | --------------- | --------------- | ------------------------------ | | `true` | `false` | Email required, phone optional | | `true` | `true` | Email required, phone required | | `false` | `true` | Email hidden, phone required | | `false` | `false` | No email or phone fields | **At least one contact field:** The API rejects `require_name: false` together with `require_email: false` and `require_phone: false` unless you pass `customer_name` or `customer_id` on the session. Checkout must be able to identify the customer from name, email, or phone. ### Common combinations | Goal | Flags | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | Phone-only checkout | `require_name: false`, `require_email: false`, `require_phone: true`, `require_billing_address: false` | | Email only (no phone) | `require_email: true`, `require_phone: false` (phone hidden when email hidden is false, phone stays optional) | | Hide name, collect email | `require_name: false`, `require_email: true` | | Minimal contact (email + optional phone) | Defaults, omit flags or set `require_phone: false` | Example, **phone-only** session: ```json { "amount": 1000, "currency_code": "XOF", "title": "Phone-only checkout", "require_name": false, "require_email": false, "require_phone": true, "require_billing_address": false } ``` The customer sees only the phone field (plus the optional WhatsApp toggle). Name, email, and billing address are hidden. **Mobile money:** When the phone field is visible but optional, **Wave** and **MTN** still require a valid phone number at payment time. Card and other methods may proceed without phone when it is optional. ### Resolution order When a customer opens checkout, flags merge from most specific to least: 1. **Checkout session** (`require_name`, `require_email`, `require_phone`, `require_billing_address` on `POST /checkout-sessions`) 2. **Payment link** (same flags on the link that created the session) 3. **Organization checkout settings** (dashboard defaults) 4. **Platform defaults** (name required, email required, phone optional, billing address hidden) Pass `customer_name`, `customer_email`, `customer_phone`, and address fields on the session to **pre-fill** the form; they do not change which fields are shown. ### Custom fields Organizations can define **custom checkout fields** (text, checkbox, terms, etc.) in dashboard checkout settings. They apply to hosted checkout, payment links, and storefront unless overridden in session or link `metadata.custom_fields`. ### Advanced: `fields` array On `POST /checkout-sessions` and `POST /payment-links`, you may pass a `fields` array, an ordered list of system and custom field definitions with `visibility` set to `hidden`, `optional`, or `required`. When present, it **overrides** the `require_*` booleans. Most integrations should use the boolean flags; use `fields` only when you need fine-grained ordering or mixed optional/required custom fields via API. ## Customer identity Checkout may **auto-create or merge** customer records when contact fields are provided, so downstream webhooks and transactions always attach to a stable `customer_id` where possible. ## Related pages * [Checkout sessions](/build/accept/checkout) * [Payment links](/build/accept/payment-links) * [Products](/build/billing/products) * [Subscriptions](/build/billing/subscriptions) * [Payment and payout lifecycle](/build/reliability/payment-lifecycle) # How do I use checkout? Source: https://docs.lomi.africa/build/accept/checkout Create a checkout session, redirect the customer to lomi., then confirm the result with redirects, dashboard records, and webhooks. *** title: 'How do I use checkout?' description: 'Create a checkout session, redirect the customer to lomi., then confirm the result with redirects, dashboard records, and webhooks.' -------------------------------------------------------------------------------------------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; Hosted checkout is the recommended first integration for most teams. Your server creates a session, your customer pays on a lomi.-hosted page, and your system confirms the result. Do not treat the create response as paid. Live Mobile Money is asynchronous. **Good to know:** Sessions expire after **60 minutes** by default. Test keys run in [sandbox mode](/start/sandbox-payments). Cross-cutting rules (coupons, line items, payment state) live in **[Checkout behavior](/build/accept/checkout-behavior)**. ## When to use hosted checkout Use hosted checkout when you want: * A complete payment page with multiple payment methods. * Less payment UI and compliance work in your own app. * A clear success and cancel redirect flow. * Support for product-based or amount-based checkout. * A reliable path from sandbox testing to live payments. ## Create a checkout session Your server calls `POST /checkout-sessions` and redirects the customer to `checkout_url`. **API reference (full request schema):** [Create checkout session](/api/checkout-sessions/CheckoutSessionsController_create) **Essential fields:** | Field | Notes | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | | `currency_code` | Required, `XOF`, `USD`, or `EUR` | | `amount` | Required unless you pass `product_id` or `line_items` | | `product_id` / `price_id` | Catalog-backed checkout; amount derived from the price | | `success_url` / `cancel_url` | Where to send the customer after pay or cancel | | `customer_email` | Optional, pre-fills the hosted form | | `require_name` / `require_email` / `require_phone` / `require_billing_address` | Optional, control checkout form fields; see [Checkout form fields](/build/accept/checkout-behavior#checkout-form-fields) | Pass `metadata` for your own order IDs. See the API page for every optional field. ### Pay what you want products When `product_id` or `price_id` points to a price with `pricing_model: pay_what_you_want`: * **Omit `amount`**: the session uses the suggested unit price (`amount` on the price row) × `quantity`. * **Pass `amount`**: must be a valid subtotal: unit price within `[minimum_amount, maximum_amount]` × `quantity`. The server rejects out-of-range values. * **Hosted checkout**: the buyer can change the unit price before paying; validation runs client-side and server-side. * **Programmatic flows**: set `amount` at session creation when you already know the chosen total. ### Multi-item checkout (`line_items`) When you pass **`line_items`**, each row must reference a **one-time** price with **standard** (fixed) pricing. The API returns **400** with one of: | Code | Meaning | | -------------------------------------- | ----------------------------------------------------------------------------- | | `line_items_pwyw_not_supported` | A line uses `pay_what_you_want` pricing, use a single-product session instead | | `line_items_recurring_not_supported` | A line references a recurring product | | `line_items_usage_based_not_supported` | Usage-based pricing (not yet supported in carts) | | `line_items_mixed_product_types` | Mixed one-time and recurring products in one cart | ### Recurring (subscription) products When `product_id` refers to a **recurring** product: * **Omit `amount`**: the session uses the catalog price for the selected `price_id` (quantity is not multiplied for subscriptions). * **Pass `amount`**: must match the catalog recurring price for validation (unless using prorated first charge at payment time; see [Checkout behavior: Subscription checkout](/build/accept/checkout-behavior#subscription-checkout)). * **Trial or `non_initial` signup**: the customer may pay **$0** at checkout, but the session record often still stores the **catalog price** so `create_checkout_session` satisfies `amount > 0`. Do not pass `amount: 0` unless you omit it and rely on product-derived pricing. * Completing checkout creates a **subscription** (`SUBSCRIPTION_CREATED` webhook), not a one-time `payment` transaction only. Example: ```typescript const subscriptionSession = await lomi.checkoutSessions.create({ product_id: 'prod_recurring_abc...', price_id: 'price_monthly_xyz...', currency_code: 'XOF', customer_email: 'customer@example.com', success_url: 'https://your-site.com/welcome', allow_coupon_code: true, }); ``` ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live', }); // Simple checkout with amount const session = await lomi.checkoutSessions.create({ amount: 10000, currency_code: 'XOF', title: 'Order #12345', description: 'Payment for items in cart', customer_email: 'customer@example.com', success_url: 'https://your-site.com/success', cancel_url: 'https://your-site.com/cancel', metadata: { order_id: 'ORD-12345', }, }); // Product-based checkout const productSession = await lomi.checkoutSessions.create({ product_id: 'prod_abc123...', currency_code: 'XOF', quantity: 2, allow_coupon_code: true, success_url: 'https://your-site.com/success', }); // PWYW product with custom amount and quantity const pwywSession = await lomi.checkoutSessions.create({ product_id: 'prod_tip_jar...', currency_code: 'XOF', amount: 1500, quantity: 2, success_url: 'https://your-site.com/success', }); console.log(`Redirect to: ${session.checkout_url}`); ``` ```python from lomi import LomiClient import os client = LomiClient( api_key=os.environ["LOMI_SECRET_KEY"], environment="test" ) # Simple checkout with amount session = client.checkout_sessions.create({ "amount": 10000, "currency_code": "XOF", "title": "Order #12345", "description": "Payment for items in cart", "customer_email": "customer@example.com", "success_url": "https://your-site.com/success", "cancel_url": "https://your-site.com/cancel", "metadata": { "order_id": "ORD-12345" } }) # PWYW product with custom amount and quantity pwyw_session = client.checkout_sessions.create({ "product_id": "prod_tip_jar...", "currency_code": "XOF", "amount": 1500, "quantity": 2, "success_url": "https://your-site.com/success" }) print(f"Redirect to: {session['checkout_url']}") ``` ```bash curl -X POST "https://api.lomi.africa/checkout-sessions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency_code": "XOF", "title": "Order #12345", "description": "Payment for items in cart", "customer_email": "customer@example.com", "success_url": "https://your-site.com/success", "cancel_url": "https://your-site.com/cancel", "metadata": { "order_id": "ORD-12345" } }' ``` ### Response ```json { "id": "cs_abc123...", "checkout_url": "https://checkout.lomi.africa/cs_abc123...", "status": "open", "amount": 10000, "currency_code": "XOF", "title": "Order #12345", "customer_email": "customer@example.com", "expires_at": "2024-01-15T11:30:00Z", "created_at": "2024-01-15T10:30:00Z" } ``` *** ## Embed instead of redirect To keep the customer on your page, use the embed SDK with the same `checkout_url`: ```javascript import { loadLomiCheckout } from '@lomi./embed'; loadLomiCheckout({ checkoutUrl: session.checkout_url, mode: 'modal', onComplete: (payload) => console.log(payload.transactionId), }); ``` See [Embed checkout widget](/build/accept/embed-widget) for modal, inline, and self-hosted setup. ## Confirm the payment Use redirects for customer experience, not final reconciliation. Your server should rely on webhooks or a server-side API read before fulfilling an order. Confirm: * The checkout session status. * The related transaction status. * The amount, currency, customer, and metadata. * The webhook event signature and event ID. ## Common variants | Variant | Use when | | ----------------------- | ---------------------------------------------- | | Fixed amount | Your backend already knows the amount | | Product checkout | The amount should come from a product or price | | Subscription checkout | The customer is starting a recurring plan | | Coupon-enabled checkout | You allow discount codes | | Test checkout | You are validating with `lomi_sk_test_...` | ## List and retrieve sessions Use the API reference to list or fetch sessions by ID: * [List checkout sessions](/api/checkout-sessions/CheckoutSessionsController_findAll) * [Get checkout session](/api/checkout-sessions/CheckoutSessionsController_findOne) ## Webhooks Merchant webhook subscriptions (lomi. calls **your** URL) use the **`webhook_event`** enum documented in [Webhooks](/build/reliability). The HTTP body field `` `event` `` and the `` `X-Lomi-Event` `` header use those values (for example `` `PAYMENT_SUCCEEDED` ``). For checkout outcomes on **your** server, subscribe at minimum to: | `authorized_events` value | When it fires | | ------------------------- | ----------------------------------------------------------------------------------------------------------- | | `` `PAYMENT_SUCCEEDED` `` | Payment completed; payload `` `data` `` follows the transaction shape (see [Webhooks](/build/reliability)). | | `` `PAYMENT_FAILED` `` | Payment attempt failed (including some provider failure paths). | There is **no** separate `` `checkout.session.*` `` event in the `` `webhook_event` `` enum. Session expiry without a successful charge is reflected on the **session** and **transaction** records (e.g. status `` `expired` ``); use `` `GET /checkout-sessions/{id}` `` or list with `` `status=expired` `` / `` `completed` `` rather than expecting a merchant webhook named like `checkout.session.expired`. *** ## Related API * [Create checkout session](/api/checkout-sessions/CheckoutSessionsController_create) * [List checkout sessions](/api/checkout-sessions/CheckoutSessionsController_findAll) * [Get checkout session](/api/checkout-sessions/CheckoutSessionsController_findOne) * [Checkout behavior](/build/accept/checkout-behavior) * [Webhooks](/build/reliability) # Digital products Source: https://docs.lomi.africa/build/accept/digital-products Upload files, deliver instantly after payment, and let customers re-download from their library. *** title: Digital products description: Upload files, deliver instantly after payment, and let customers re-download from their library. ------------------------------------------------------------------------------------------------------------- # Digital products Sell downloadable files (PDFs, templates, courses) with automatic fulfillment after payment. ## Setup 1. Open **Catalog** and create or edit a product. 2. Set **Fulfillment** to **Digital** or **Hybrid**. 3. Open the **Files** tab and upload deliverables (up to 500 MB per file). 4. Share via payment link, storefront, or embed widget. ## Buyer experience After a successful payment, buyers receive: * Download links on the checkout success page * A delivery email with file links * A permanent library at [customers.lomi.africa](https://customers.lomi.africa) to re-download purchases ## Webhooks Subscribe to `PURCHASE_FULFILLED` to run automations when digital entitlements are granted. Payload fields: * `transaction_id`, completed payment * `customer_id`, buyer * `product_ids`, digital products fulfilled * `entitlement_ids`, file entitlements granted ## File updates When you add or remove deliverable files on a product with past sales, existing buyers receive updated entitlements and an email with refreshed download links. # How do I use charges? Source: https://docs.lomi.africa/build/accept/direct-charges Server-initiated Wave, MTN, and card charges when you need full control over the payment UI. *** title: 'How do I use charges?' description: 'Server-initiated Wave, MTN, and card charges when you need full control over the payment UI.' ----------------------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; Direct charges let you collect payment from your server using `POST /charge/*`. Unlike hosted [checkout sessions](/build/accept/checkout), you build the customer-facing flow yourself. **Card and Switch direct charges are not available yet.** `POST /charge/card` and `POST /charge/switch` are turned off while the card rails are being finalized, and calling them today returns `503 service_unavailable`. Use [hosted checkout](/build/accept/checkout) or [payment links](/build/accept/payment-links) for cards. Only Wave and MTN mobile money direct charges are live. Prefer [hosted checkout](/build/accept/checkout) or [payment links](/build/accept/payment-links) unless you need a custom mobile-money flow. Direct charges require you to handle pending states and webhooks. ## When to use direct charges Use direct charges when you want to charge quickly and will not reuse the same payment method object for repeat charges, similar to a one-shot orchestrated payment. | Rail | Endpoint | Customer step | | ------------------------- | --------------------- | -------------------------------------------------------------------- | | Wave | `POST /charge/wave` | Open `wave_launch_url` or `checkout_url` from the response | | MTN | `POST /charge/mtn` | Approve on their phone; live status starts `PENDING` | | Card | `POST /charge/card` | **Not available yet**: use [hosted checkout](/build/accept/checkout) | | Switch (server-side card) | `POST /charge/switch` | **Not available yet**: use [hosted checkout](/build/accept/checkout) | ## Reference implementation The monorepo includes a runnable example: * Path: `apps/plugins/references/direct-charge-integration-reference` * Routes: Wave, MTN, card, and webhook verification * cURL scripts: `curl/create-wave-charge.sh`, `curl/create-mtn-charge.sh` ```bash cd apps/plugins/references/direct-charge-integration-reference pnpm install && cp .env.example .env pnpm run dev ``` ## Card flow (summary) Not available yet. The steps below describe the planned card flow; `POST /charge/card` and `POST /charge/switch` currently return `503 service_unavailable`. Use [hosted checkout](/build/accept/checkout) for cards. 1. `POST /charge/card` with amount, currency, and `customer_id` or (`customer_email` + `customer_name`). 2. Mount Payment Elements with `lomi_pk_...` and the returned `client_secret`. 3. Listen for `PAYMENT_SUCCEEDED` webhooks for fulfillment. ## Mobile money flow (summary) 1. `POST /charge/wave` or `POST /charge/mtn` with amount, currency, and customer phone in E.164 format. 2. Redirect or instruct the customer per the response. 3. Before you provide value, confirm the transaction's final **status** and **amount** via webhooks or `GET /transactions/{id}`. See [Mobile money](/build/mobile-money) for the full guide. ### Wave request fields `POST /charge/wave` | Field | Type | Required | Description | | ---------------------- | -------- | -------- | ------------------------------------------- | | `amount` | `number` | Yes | Amount to charge (minimum 100). | | `currency` | `string` | Yes | Must be `XOF`. | | `customer` | `object` | Yes | Customer details. | | `customer.name` | `string` | Yes | Full name. | | `customer.email` | `string` | No | Email. | | `customer.phoneNumber` | `string` | No | E.164 phone (for example `+2250102030405`). | | `description` | `string` | No | Charge description. | | `successUrl` | `string` | No | Redirect after success. | | `errorUrl` | `string` | No | Redirect after failure. | | `environment` | `string` | No | `live` or `test` (defaults to `live`). | A `201` response includes `transactionId`, `checkoutUrl`, and Wave session details. `POST /charge/mtn` follows the same pattern; see [Create MTN charge](/api/charge/ChargesController_createMtnCharge). Card endpoints (`POST /charge/card`, `GET /charge/card/{id}`, `POST /charge/card/{id}/cancel`) are documented in the REST reference. They currently return `503 service_unavailable`. ## Sandbox scenarios (test key only) With a **test** API key, direct Wave and MTN charges auto-complete by default. To test async or failure paths in CI, send `X-Scenario-Key`: | Value | Result | | --------- | ----------------------------------------- | | `pending` | Charge stays `PENDING` (no auto-complete) | | `failed` | `400` error | ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/mtn" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "X-Scenario-Key: pending" \ -H "Content-Type: application/json" \ -d '{"amount":1000,"currency":"XOF","customer_phone":"+2250700000000"}' ``` Hosted checkout and card direct charges do **not** support this header yet. See [Simulate errors](/build/reliability/simulate-errors) and [Sandbox payments](/start/sandbox-payments#testing-mobile-money). ## lomi. Network Operators can create direct charges on behalf of connected member accounts: ```http POST /charge/wave HTTP/1.1 X-API-KEY: lomi_sk_live_operator_... Lomi-Account: acct_1234567890 ``` See [lomi. Network](/build/platform/network). ## Troubleshooting Use `GET /providers` to confirm which rails are connected for the organization before calling direct charge endpoints. | Symptom | Likely cause | Fix | | -------------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Card charge returns `503 service_unavailable` | Card direct charges are not configured on the API deployment | **Ops:** ensure platform card-payment credentials are set on the API service for the target environment. **Merchants do not configure card-processor account keys**: card direct charges run on lomi.'s infrastructure. | | Wave charge returns `400` about missing Aggregated Merchant ID | Wave is not fully connected in the dashboard | Connect Wave in the dashboard and save the **Aggregated Merchant ID** for the organization. | | Wave charge returns `400` with a Wave edge error | Invalid payload or Wave-side rejection | Send `currency: "XOF"`, nested `customer.phoneNumber` in E.164, and optional `successUrl` / `errorUrl` (camelCase). See [Wave](/build/payment-methods/wave). | | Card charge returns `400` about customer fields | Missing reconciliation fields | Include `customer_id`, or both `customer_email` and `customer_name`. | | Hosted checkout works but direct charge fails | Different prerequisites per rail | Hosted checkout uses lomi.'s checkout app; direct charges hit `/charge/*` and require correct payload shape plus provider connection. | Merchants only need `lomi_sk_...` (server) and `lomi_pk_...` (Payment Elements). They never configure platform card-payment secrets themselves. ## Related * [Create Wave charge](/api/charge/ChargesController_createWaveCharge) * [Create MTN charge](/api/charge/ChargesController_createMtnCharge) * [Create card charge](/api/charge/ChargesController_createCardCharge) * [Handling webhooks](/build/reliability/handling-webhooks) # Embed checkout widget Source: https://docs.lomi.africa/build/accept/embed-widget Embed lomi. hosted checkout on your site with @lomi./embed, modal overlay or inline iframe. *** title: Embed checkout widget description: Embed lomi. hosted checkout on your site with @lomi./embed, modal overlay or inline iframe. -------------------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; # Embed checkout widget Keep customers on your site while they pay on lomi.'s hosted checkout page inside an iframe. Use **modal** for a pay button overlay, or **inline** to embed checkout in a product page. Embed is best when you want the full lomi. checkout UI (Wave, MTN, cards, coupons) without a redirect. For custom card fields inside your own form, see [lomi. Payment Elements](/build/accept/payment-elements). ## When to use embed vs redirect vs Payment Elements | Approach | Best for | | -------------------------------------------------------- | ------------------------------------------------------------ | | **Redirect** ([hosted checkout](/build/accept/checkout)) | Simplest integration; customer leaves your site briefly | | **Embed** (this guide) | Same checkout UI, stays on your page | | **Payment Elements** | You own the UI; collect cards or mobile money in your layout | ## Step 1: Create a checkout session Your server creates the session and returns `checkout_url` to the browser. **curl** ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/checkout-sessions" \ -H "X-API-Key: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency_code": "XOF", "success_url": "https://example.com/success", "cancel_url": "https://example.com/cancel" }' ``` **TypeScript SDK** ```typescript import { lomiApi } from './lib/lomi/client'; const session = await lomiApi.createCheckoutSession({ success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', amount: 10000, currency_code: 'XOF', }); // Pass session.checkout_url to the embed SDK ``` **CLI** ```bash lomi checkout create ``` The CLI prints the redirect URL and a ready-to-paste embed snippet. ## Step 2: Install @lomi./embed ```bash npm install @lomi./embed ``` ## Modal example (bundler) ```html ``` The SDK adds `embedded=true` and `embed_origin` automatically, do not append them to `checkoutUrl`. ## Inline example ```html
``` Declarative attributes: | Attribute | Description | | ------------------------ | ---------------------------------------------- | | `data-lomi-checkout-url` | Preferred, full URL from API or payment link | | `data-lomi-session-id` | Alternative with `data-lomi-checkout-base-url` | | `data-lomi-public-key` | Optional when using `checkoutUrl` | | `id` | Required container id (e.g. `lomi-checkout`) | ## Self-hosted script (no CDN) There is **no** hosted CDN. Copy the IIFE bundle from the package: ```bash cp node_modules/@lomi./embed/dist/lomi.js public/assets/lomi.js ``` ```html ``` Payment links on `checkout.lomi.africa` work the same way, pass the link URL as `checkoutUrl`. ## Callbacks and events | Callback | When | | ------------ | ----------------------------------------------------- | | `onComplete` | Payment succeeded (after fulfillment when applicable) | | `onResize` | Iframe height changed (inline mode) | | `onError` | Checkout reported an error | The iframe sends `LOMI_CHECKOUT` messages; legacy types `LOMI_CHECKOUT_COMPLETE` and `LOMI_RESIZE` are supported. Messages from origins other than the checkout host are ignored. `onComplete` payload: ```typescript { type: 'LOMI_CHECKOUT_COMPLETE'; sessionId?: string; transactionId?: string; amount?: number; currency?: string; hasDigitalDeliverables?: boolean; } ``` ## Webhooks Embed callbacks improve UX only. Always reconcile orders with [webhooks](/build/reliability) or a server-side API read before fulfilling. ## Sandbox testing Use sandbox API keys and pass a sandbox `checkout_url`. For local development, point at your checkout app: ```javascript loadLomiCheckout({ checkoutUrl: 'http://localhost:3000/checkout/cs_test_...', mode: 'modal', }); ``` See [Sandbox payments](/start/sandbox-payments) for test payment methods. ## Related * [Hosted checkout](/build/accept/checkout): redirect flow * [Payment links](/build/accept/payment-links): shareable URLs (also work as `checkoutUrl`) * [SDKs overview](/build/sdks): `@lomi./sdk` and `@lomi./embed` * Package README, `node_modules/@lomi./embed/README.md` # Accept payments Source: https://docs.lomi.africa/build/accept Hosted checkout, payment links, requests, embed, direct charges, and related payment surfaces. *** title: Accept payments description: Hosted checkout, payment links, requests, embed, direct charges, and related payment surfaces. index: true ----------- Choose how customers pay. Hosted checkout is the default for most teams. Use a more specific page when you need a shareable link, an invoice, an embed, or a server-initiated charge. # lomi. Payment Elements Source: https://docs.lomi.africa/build/accept/payment-elements Accept payments directly in your app with lomi.'s white-labeled payment infrastructure. *** title: lomi. Payment Elements description: Accept payments directly in your app with lomi.'s white-labeled payment infrastructure. ---------------------------------------------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; # lomi. Payment Elements lomi. Payment Elements allow you to accept card payments directly within your application without redirecting users to a hosted checkout page. This gives you full control over the user experience while lomi. handles the payment processing complexity. lomi. Payment Elements are powered by secure, PCI-compliant infrastructure. You never touch raw card data. ## Overview There are three ways to integrate lomi. Payments: | Method | Use Case | | ------------------------------------------- | ------------------------------------------------------------- | | **TypeScript SDK (`@lomi./sdk`)** | Apps web (Vanilla JS, React, Vue, etc.) pour Payment Elements | | **React Native SDK (`@lomi/react-native`)** | Native mobile apps for iOS and Android | | **Embedded Checkout (`@lomi./embed`)** | iframe-based integration preserving lomi.'s full UI | *** ## Installation `bash npm install @lomi./sdk ` `bash npm install @lomi/react-native ` `bash npm install @lomi./embed ` *** ## Quick start ### 1. Get Your API Keys You'll need two keys from your [lomi. Dashboard](https://lomi.africa/dashboard/settings/api-keys): * **Publishable Key** (`lomi_pk_...`): Used client-side to initialize the SDK * **Secret Key** (`lomi_sk_...`): Used server-side to create Payment Intents Never expose your Secret Key in client-side code! ### 2. Create a Payment Intent (Server-Side) Before collecting payment, create a Payment Intent on your server: ```javascript // Your server (Node.js example) const response = await fetch('https://api.lomi.africa/charge/card', { method: 'POST', headers: { 'X-API-KEY': process.env.LOMI_SECRET_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ amount: 10000, // Amount in smallest currency unit (e.g., 10000 XOF) currency_code: 'XOF', customer_email: 'customer@example.com', customer_name: 'Ada Lovelace', }), }); const body = await response.json(); const clientSecret = body.data.client_secret; // Pass clientSecret to your frontend ``` Detailed endpoint docs: [Create card charge](/api/charge/ChargesController_createCardCharge) ### 3. Collect Payment (Client-Side) ```javascript import { loadLomi } from '@lomi./sdk'; // Initialize Lomi const lomi = await loadLomi('lomi_pk_your_publishable_key'); // Create payment elements const elements = lomi.elements({ clientSecret }); const paymentElement = elements.create('payment'); paymentElement.mount('#payment-element'); // Handle form submission form.addEventListener('submit', async (e) => { e.preventDefault(); const { error } = await lomi.confirmPayment({ elements, confirmParams: { return_url: 'https://yoursite.com/success', }, }); if (error) { console.error(error.message); } }); ``` ```tsx import { LomiProvider, LomiCardField, useLomi } from '@lomi/react-native'; function App() { return ( ); } function PaymentScreen() { const { confirmPayment } = useLomi(); const handlePay = async () => { const { error } = await confirmPayment(clientSecret, { paymentMethodType: 'Card', }); if (error) { console.error(error.message); } }; return ( <> ``` See [Embed checkout widget](/build/accept/embed-widget) for inline mode and self-hosted `dist/lomi.js`. ### `lomi payments create` Create a payment link via the payment links API. ```bash lomi payments create lomi payments create --title "Invoice #42" --amount 5000 --currency XOF --json ``` Interactive mode prompts for: title, amount, currency. Headless mode requires all three flags. *** ## AI agent rules ### `lomi install-rules` Install lomi. documentation for AI coding tools. See [Install agent rules](/build/cli/install-rules). ```bash lomi install-rules lomi install-rules --target cursor --target llms.txt lomi install-rules --force ``` | Target | Output | | ------------- | --------------------------------------------- | | `cursor` | `.cursor/rules/lomi.*.mdc` | | `claude-code` | `CLAUDE.md` | | `codex` | `AGENTS.md` | | `vscode` | `.github/instructions/lomi-*.instructions.md` | | `llms.txt` | Project-root `llms.txt` | *** ## Typical workflows ### New integration from scratch ```bash npm install -g lomi.cli lomi login lomi quickstart mkdir my-store && cd my-store lomi init lomi listen http://localhost:3000/webhooks ``` ### Sandbox vs production profiles ```bash lomi login --profile sandbox lomi login --profile production lomi switch sandbox lomi init --profile sandbox ``` ### CI pipeline ```bash export LOMI_ACCESS_TOKEN=$CLI_TOKEN lomi status lomi init --yes -e sandbox -L ts --api-key $LOMI_SECRET_KEY --skip-package-install --skip-rules-install ``` ## Help ```bash lomi --help lomi login --help lomi init --help lomi dev --help lomi install-rules --help ``` *** ## Maintainers The `lomi docs` command is hidden from default `--help`. It is for **lomi. contributors** working on documentation in the monorepo, not for merchant integrators. ### `lomi docs check` Run docs lint and OpenAPI drift checks from the monorepo root: ```bash lomi docs check ``` Equivalent to `cd apps/docs && pnpm lint && pnpm docs:drift`. See [Writing for lomi. docs](/resources/contributing/writing-for-lomi-docs). # Configuration Source: https://docs.lomi.africa/build/cli/configuration Global CLI config, project environment variables, profiles, and CI settings for lomi.. CLI. *** title: 'Configuration' description: 'Global CLI config, project environment variables, profiles, and CI settings for lomi.. CLI.' ---------------------------------------------------------------------------------------------------------- lomi. CLI uses two layers of configuration: **global** (authentication and profiles) and **project-level** (SDK keys and app settings). ## Global CLI config Stored after [`lomi login`](/build/cli/auth): | Platform | Path | | -------- | ------------------------------------------------ | | macOS | `~/Library/Application Support/lomi/config.json` | | Linux | `~/.config/lomi/config.json` | Override with: ```bash filename="Terminal" export LOMI_CONFIG_DIR=/custom/path/lomi ``` ### Config file shape ```json filename="config.json" { "version": 2, "current_profile": "default", "profiles": { "default": { "cli_token": "lomi_sk_...", "api_url": "https://api.lomi.africa" }, "sandbox": { "cli_token": "lomi_sk_test_...", "api_url": "https://sandbox.api.lomi.africa" } }, "settings": { "has_seen_rules_install_prompt": true, "last_rules_install_version": "1.0.0" } } ``` Manage profiles with [`lomi list-profiles`](/build/cli/commands#lomi-list-profiles), [`lomi switch`](/build/cli/commands#lomi-switch-profile), and [`lomi logout`](/build/cli/commands#lomi-logout). ## Environment variables ### CLI authentication | Variable | Description | | ------------------------ | -------------------------------------------------------------- | | `LOMI_ACCESS_TOKEN` | CLI token for CI/headless use (overrides stored profile token) | | `LOMI_CONFIG_DIR` | Override global config directory | | `LOMI_SUPABASE_ANON_KEY` | Override Supabase anon key for device auth (optional) | ### Project SDK (via `.env`) Set by [`lomi init`](/build/cli/init) in your project root: | Variable | Description | | --------------------- | ---------------------------------------------------------------- | | `LOMI_SECRET_KEY` | Secret API key for the SDK (`lomi_sk_test_…` / `lomi_sk_live_…`) | | `LOMI_WEBHOOK_SECRET` | Webhook signing secret for signature verification | | `LOMI_API_URL` | API base URL (`https://api.lomi.africa` or sandbox) | **`LOMI_ACCESS_TOKEN`**: authenticates the **CLI** (from `lomi login`). **`LOMI_SECRET_KEY`**: authenticates your **application SDK** (from the Merchant Portal, stored in `.env` by `lomi init`). ## Project config file `lomi init` creates `lomi.config.ts` for project-level settings used by examples and tooling. ## Global CLI flags Override config per command: ```bash filename="Terminal" lomi --profile sandbox status lomi -a https://sandbox.api.lomi.africa checkout create lomi --log-level debug dev ``` | Flag | Description | | ------------------ | --------------------------------- | | `--profile` | Profile name (default: `default`) | | `-a, --api-url` | API base URL override | | `-l, --log-level` | Log verbosity | | `--skip-telemetry` | Opt out of telemetry | ## API URLs | Environment | URL | | ----------- | --------------------------------- | | Production | `https://api.lomi.africa` | | Sandbox | `https://sandbox.api.lomi.africa` | Profiles `sandbox` and `default` map to the appropriate URL automatically during login. ## CI example ```bash filename="Terminal" export LOMI_ACCESS_TOKEN="${{ secrets.LOMI_CLI_TOKEN }}" export LOMI_SECRET_KEY="${{ secrets.LOMI_SECRET_KEY }}" lomi status lomi init --yes \ --environment sandbox \ --language ts \ --api-key "$LOMI_SECRET_KEY" \ --skip-package-install \ --skip-rules-install ``` Authenticate Initialize a project Authentication reference # Local development server Source: https://docs.lomi.africa/build/cli/dev Run lomi dev to receive and inspect webhook events on your machine during development. *** title: 'Local development server' description: 'Run lomi dev to receive and inspect webhook events on your machine during development.' ----------------------------------------------------------------------------------------------------- The `lomi dev` command starts a lightweight local HTTP server that receives webhook POST requests and prints events in the terminal. For real sandbox webhooks without ngrok, prefer [`lomi listen`](/build/cli/listen). Requires [`lomi login`](/build/cli/auth). ## Usage ```bash filename="Terminal" lomi dev ``` Default URL: **`http://localhost:4242/webhook`** ### Options | Flag | Default | Description | | ---------------------- | -------------------------------------- | ----------------------------------------------------- | | `-p, --port` | `4242` | Port for the HTTP server | | `--env-file` | `.env` | Dotenv file to load | | `--verify-signature` | auto when `LOMI_WEBHOOK_SECRET` is set | Verify `X-Lomi-Signature` using `LOMI_WEBHOOK_SECRET` | | `--skip-rules-install` | - | Skip agent rules install prompt | ```bash filename="Terminal" lomi dev --port 3000 lomi dev --verify-signature ``` ## Endpoints | Method | Path | Description | | ------ | ---------- | ------------------------------- | | `POST` | `/webhook` | Receives webhook event payloads | | `GET` | `/health` | Returns `ok` | ## Testing locally ### Start the dev server ```bash filename="Terminal" lomi dev ``` ### Send a test event ```bash filename="Terminal" curl -X POST http://localhost:4242/webhook \ -H "Content-Type: application/json" \ -d '{"type":"test.event","data":{}}' ``` The CLI prints the event type and payload in the terminal. ### Receive real sandbox events (recommended) ```bash filename="Terminal" lomi listen http://localhost:4242/webhook ``` ## Signature verification When verification is enabled, the server validates the `X-Lomi-Signature` header against `LOMI_WEBHOOK_SECRET` from your `.env` file. Ensure `LOMI_WEBHOOK_SECRET` is set before enabling verification: ```dotenv filename=".env" LOMI_WEBHOOK_SECRET=whsec_your_secret_here ``` For production webhook handling patterns, see [Handling webhooks](/build/reliability/handling-webhooks) and [Webhooks reference](/build/reliability). ## Relationship to `lomi init` Running `lomi dev` after [`lomi init`](/build/cli/init) loads credentials from the project `.env`. The generated `examples/webhook-handler.ts` shows how to implement verification in your own application code. Handling webhooks Webhook reliability Webhooks # lomi. CLI Source: https://docs.lomi.africa/build/cli Install and use lomi. command-line tool for authentication, project scaffolding, local webhook development, payments, and AI agent rules. *** title: lomi. CLI description: Install and use lomi. command-line tool for authentication, project scaffolding, local webhook development, payments, and AI agent rules. ------------------------------------------------------------------------------------------------------------------------------------------------------ import { Callout } from '@/components/docs/docs-callout'; The **lomi. CLI** is distributed via **npm** as `lomi.cli`. It helps you authenticate with lomi., scaffold SDK projects, test webhooks locally, create checkout sessions and payment links, and install AI agent rules. The lomi. CLI is for **developers integrating** the hosted merchant API from your terminal: authenticate, scaffold SDK projects, test webhooks, and create sandbox checkouts. First-run (npm install, login, first checkout) lives on the [CLI quickstart](/start/cli-quickstart). This page is the full install reference (Homebrew, source, platforms) and the rest of the command set. It is **not** for operating lomi.’s internal platform, the admin dashboard, or self-hosting payment processing. Merchants who only use the web dashboard do not need the CLI. Both `lomi` and `lomi.` work as command names after install. ## Installation ### npm (recommended) Requires **Node.js 18+** (used only to download the native binary on install). ```bash filename="Terminal" npm install -g lomi.cli ``` Or with pnpm: ```bash filename="Terminal" pnpm add -g lomi.cli ``` On `npm install`, the package downloads the correct native binary for your platform from [GitHub Releases](https://github.com/lomiafrica/lomi./releases). ### Homebrew Homebrew support is available via the project formula. After a release is published: ```bash filename="Terminal" brew tap lomiafrica/tap brew install lomi ``` You can also install from the repo formula when developing locally (update SHA256 checksums after each release). ### From source ```bash filename="Terminal" git clone https://github.com/lomiafrica/lomi. cd lomi./apps/cli cargo install --path . ``` ### Verify installation ```bash filename="Terminal" lomi --version lomi --help ``` ### Troubleshooting install | Symptom | Likely cause | What to do | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `lomi: command not found` after `npm install -g` | npm global bin not on `PATH` | Run `npm prefix -g` and add that `bin` directory to your shell `PATH`, or use `npx lomi.cli --version` | | Installed but `lomi --version` fails with **glibc** / **version \`GLIBC\_X.XX' not found** | Prebuilt binary targets a newer glibc than your Linux distro | Install via [Homebrew](#homebrew), download a matching [GitHub Release](https://github.com/lomiafrica/lomi./releases) binary, or [build from source](#from-source) with Rust on the same machine | | Binary runs on macOS but not on older Linux CI images | CI runners often ship older glibc | Use the source build in CI, or a newer runner image (glibc ≥ 2.39 for npm-published Linux binaries at time of writing) | npm only needs **Node.js 18+** to download the native binary; the CLI itself does not run on Node. If `npm install -g lomi.cli` succeeds but the binary cannot execute, treat it as a **platform compatibility** issue, not a Node version issue. ### Supported platforms | Platform | Install method | | --------------------- | ----------------------------- | | macOS (Apple Silicon) | npm, Homebrew, GitHub Release | | macOS (Intel) | npm, Homebrew, GitHub Release | | Linux (x64) | npm, Homebrew, GitHub Release | | Windows (x64) | Coming soon | ## Quick start ### Log in ```bash filename="Terminal" lomi login ``` Opens a browser device-authorization flow and saves a CLI token globally. See [Authenticate](/build/cli/auth). ### Check status ```bash filename="Terminal" lomi status ``` Verifies your stored token and connectivity to lomi.. API. ### Scaffold a project ```bash filename="Terminal" cd my-app lomi init ``` Creates SDK client setup, checkout and webhook examples, and a `.env` file. See [Initialize a project](/build/cli/init). ### Install AI agent rules (optional) ```bash filename="Terminal" lomi install-rules ``` Installs Cursor rules, `CLAUDE.md`, `AGENTS.md`, VS Code instructions, and `llms.txt`. See [Install agent rules](/build/cli/install-rules). ### Listen for webhooks (recommended) ```bash filename="Terminal" lomi listen http://localhost:3000/webhooks ``` Receives real sandbox webhooks via cloud relay, no ngrok. See [Listen for webhooks](/build/cli/listen). ### Run local webhook server ```bash filename="Terminal" lomi dev ``` Starts a webhook receiver on `http://localhost:4242/webhook`. See [Local development server](/build/cli/dev). ## Command overview | Command | Description | | ----------------------------------- | -------------------------------------- | | `lomi login` | Browser authentication | | `lomi logout` | Clear credentials for a profile | | `lomi whoami` | Show current profile | | `lomi status` | Check login and API connectivity | | `lomi init` | Scaffold SDK project files | | `lomi quickstart` | Golden-path checks and next steps | | `lomi listen` | Cloud webhook relay (sandbox-first) | | `lomi dev` | Local webhook development server | | `lomi probe` | Integration health checks | | `lomi webhooks list` | List webhook endpoints | | `lomi webhooks test` | Send a test webhook event | | `lomi products list` | List products and prices | | `lomi transactions list` | List recent transactions | | `lomi transactions get` | Get a transaction by ID | | `lomi refunds create` | Refund a completed transaction | | `lomi refunds list` / `refunds get` | List or retrieve refunds | | `lomi checkout create` | Create a hosted checkout session | | `lomi payments create` | Create a payment link | | `lomi install-rules` | Install AI agent documentation | | `lomi update` | Update `@lomi./sdk` in current project | | `lomi list-profiles` | List saved auth profiles | | `lomi switch` | Change active profile | Full reference: [Command reference](/build/cli/commands). ## Global options These flags work with any command: | Flag | Description | | ------------------------- | ------------------------------------------------------------------ | | `--profile ` | Profile to use (default: `default`) | | `-a, --api-url ` | Override API base URL | | `-l, --log-level ` | `debug`, `info`, `warn`, `error` | | `--skip-telemetry` | Opt out of telemetry | | `--json` | Emit machine-readable JSON (also default when stdout is not a TTY) | | `-h, --help` | Help for the CLI or a subcommand | | `--version` | Print version | ## Two kinds of credentials **`lomi login`** stores a **CLI token** in your global config, used by CLI commands like `status`, `checkout create`, and `dev`. **`lomi init`** writes your **secret API key** (`LOMI_SECRET_KEY`) into the project `.env` file, used by the **SDK inside your application code**. These are different credentials serving different purposes. ## More CLI pages
# Initialize a project Source: https://docs.lomi.africa/build/cli/init Scaffold a lomi. integration with SDK client, checkout and webhook examples, and environment variables. *** title: 'Initialize a project' description: 'Scaffold a lomi. integration with SDK client, checkout and webhook examples, and environment variables.' ---------------------------------------------------------------------------------------------------------------------- The `lomi init` command bootstraps a lomi. integration in your project directory. It creates SDK client setup, example code, and a `.env` file with your API credentials. Requires prior [`lomi login`](/build/cli/auth) (or `LOMI_ACCESS_TOKEN` in CI). ## Usage ```bash filename="Terminal" cd my-project lomi init ``` ### Options | Flag | Description | | ------------------------ | ------------------------------------------------------------------------ | | `-y, --yes` | Headless mode (requires `--environment`, `--language`, `--api-key`) | | `-e, --environment` | `production` or `sandbox` | | `-L, --language` | `ts` or `js` | | `--api-key` | Secret API key written to `.env` | | `--skip-package-install` | Skip `npm install` of SDK dependencies | | `--skip-rules-install` | Skip agent rules install (`--yes` installs Cursor + llms.txt by default) | | `[path]` | Project directory (default: current directory) | ### Headless example (CI) ```bash filename="Terminal" lomi init --yes \ --environment sandbox \ --language ts \ --api-key lomi_sk_test_xxx \ --skip-package-install \ --skip-rules-install ``` ## Interactive flow When run interactively, `lomi init` prompts for: 1. **Environment**: Production or Sandbox (sets `LOMI_API_URL`) 2. **Language**: TypeScript or JavaScript for generated examples 3. **API key**: Your secret key from the [Merchant Portal](https://lomi.africa/portal/settings/api-keys) It may also offer to run [`lomi install-rules`](/build/cli/install-rules) if you haven't installed agent rules yet. ## Files created | File | Purpose | | ------------------------------------------- | ------------------------------------------------------------------------ | | `.env` | `LOMI_SECRET_KEY`, `LOMI_WEBHOOK_SECRET` placeholder, `LOMI_API_URL` | | `lib/lomi./client.{ts\|js}` | Initialized SDK client loading from `.env` | | `examples/create-checkout-session.{ts\|js}` | Checkout session creation example | | `examples/webhook-handler.{ts\|js}` | Basic webhook handler example | | `examples/embed-checkout.html` | Modal embed example, replace `CHECKOUT_URL` after `lomi checkout create` | | `lomi.config.ts` | Project-level lomi. configuration | Example `.env`: ```dotenv filename=".env" LOMI_SECRET_KEY=lomi_sk_test_xxx LOMI_WEBHOOK_SECRET=whsec_your_webhook_secret_here LOMI_API_URL=https://sandbox.api.lomi.africa ``` Add `.env` to your `.gitignore`. Never commit secret API keys or webhook secrets. ## Dependencies If `package.json` exists (or is created), `lomi init` installs: * `@lomi./sdk` * `dotenv` Use `--skip-package-install` to skip this step in CI and install manually. ## After init ### Review generated examples Open `lib/lomi./client.ts` and the files in `examples/`. ### Add your webhook secret Replace the `LOMI_WEBHOOK_SECRET` placeholder in `.env` with the signing secret from the Merchant Portal. ### Test webhooks locally ```bash filename="Terminal" lomi dev ``` Point your dashboard webhook URL to a tunnel (e.g. ngrok) or use the local server during development. See [Local development server](/build/cli/dev). ### Verify CLI auth ```bash filename="Terminal" lomi status ``` ## Prerequisites * [`lomi login`](/build/cli/auth) completed (or `LOMI_ACCESS_TOKEN` set) * A **secret API key** from the Merchant Portal (`lomi_sk_test_…` or `lomi_sk_live_…`) Local development server Handling webhooks Checkout sessions TypeScript SDK # Install agent rules Source: https://docs.lomi.africa/build/cli/install-rules Use lomi install-rules to add lomi. SDK and API documentation for Cursor, Claude Code, Codex, VS Code, and llms.txt. *** title: 'Install agent rules' description: 'Use lomi install-rules to add lomi. SDK and API documentation for Cursor, Claude Code, Codex, VS Code, and llms.txt.' ----------------------------------------------------------------------------------------------------------------------------------- The `lomi install-rules` command installs versioned lomi. documentation into your project so AI coding assistants understand checkout sessions, webhooks, payment intents, subscriptions, and the API reference. Inspired by the agent-rules pattern used by tools like Trigger.dev, your AI reads these files automatically when you work in the repo. ## Usage ```bash filename="Terminal" lomi install-rules ``` Interactive mode shows a multi-select wizard: * **Cursor**: `.cursor/rules/lomi.*.mdc` * **Claude Code**: appends to `CLAUDE.md` * **OpenAI Codex**: appends to `AGENTS.md` * **VS Code**: `.github/instructions/lomi-*.instructions.md` * **llms.txt**: project-root briefing synced from [docs.lomi.africa/llms.txt](https://docs.lomi.africa/llms.txt) ### Non-interactive ```bash filename="Terminal" lomi install-rules --target cursor --target llms.txt lomi install-rules --target claude-code --target codex --target vscode lomi install-rules --force ``` | Flag | Description | | ----------------- | ------------------------------------------------------------------------------- | | `--target ` | Repeat for each target (`cursor`, `claude-code`, `codex`, `vscode`, `llms.txt`) | | `--force` | Reinstall even if already up to date | In CI or non-TTY environments, defaults to **Cursor + llms.txt** when no targets are specified. ## Rule topics Each target receives bundled rules for: | Topic | Covers | | ----------------- | ----------------------------------------- | | SDK basics | Client setup, authentication, environment | | Checkout sessions | Hosted checkout flow | | Webhooks | Signature verification, event handling | | Payment intents | Card and Elements-style flows | | Subscriptions | Recurring billing | | API reference | Generated from OpenAPI | | Docs writing | Editorial contract for docs.lomi.africa | Cursor rules include YAML frontmatter (`description`, `globs`, `alwaysApply`) so Cursor picks them up automatically. ## When rules are offered * Running `lomi install-rules` directly * First run of [`lomi init`](/build/cli/init) (unless `--skip-rules-install`) * First run of [`lomi dev`](/build/cli/dev) (unless `--skip-rules-install`) The CLI tracks the installed rules version in your global config and may prompt again when a new version ships. ## Example: Cursor setup ```bash filename="Terminal" cd my-project lomi install-rules --target cursor ``` Creates files like: ``` .cursor/rules/ lomi.sdk-basics.mdc lomi.checkout-sessions.mdc lomi.webhooks.mdc lomi.charges.mdc lomi.subscriptions.mdc lomi.api-reference.mdc ``` ## Example: full AI stack ```bash filename="Terminal" lomi install-rules \ --target cursor \ --target claude-code \ --target codex \ --target vscode \ --target llms.txt ``` ## Related docs For manual AI setup without the CLI, see [API integration: agent setup](/start/first-payment). Getting started Initialize a project TypeScript SDK # Listen for webhooks Source: https://docs.lomi.africa/build/cli/listen Use lomi listen to receive sandbox webhooks via cloud relay without ngrok. *** title: 'Listen for webhooks' description: 'Use lomi listen to receive sandbox webhooks via cloud relay without ngrok.' ----------------------------------------------------------------------------------------- `lomi listen` opens a Server-Sent Events (SSE) connection to lomi.. API and streams real webhook events to your machine. Optionally forward them to a local HTTP handler. Requires [`lomi login`](/build/cli/auth) with a **sandbox** profile (recommended). ## Usage ```bash filename="Terminal" # Print events in the terminal lomi listen # Forward to your local webhook handler lomi listen http://localhost:3000/api/webhooks ``` ### Options | Flag | Description | | -------------------- | ------------------------------------------------------------------------------------------ | | `FORWARD_URL` | Optional URL to POST signed webhook payloads | | `--allow-production` | Opt in to listen on a production profile (requires API `CLI_LISTEN_ALLOW_PRODUCTION=true`) | ## Workflow ### Log in (sandbox) ```bash filename="Terminal" lomi login --profile sandbox lomi switch sandbox ``` ### Start listening ```bash filename="Terminal" lomi listen http://localhost:3000/webhooks ``` On connect, the CLI prints your organization and `LOMI_WEBHOOK_SECRET`. Add the secret to `.env`: ```dotenv filename=".env" LOMI_WEBHOOK_SECRET=whsec_... ``` ### Trigger a test event ```bash filename="Terminal" lomi webhooks list lomi webhooks test ``` Events appear in the terminal and are forwarded to your local URL with production headers (`X-Lomi-Signature`, `X-Lomi-Event`). ## Compared to `lomi dev` | | `lomi listen` | `lomi dev` | | ---------------------------- | --------------------- | ------------------------------------------- | | Receives real sandbox events | Yes | Only if dashboard points to localhost/ngrok | | Requires ngrok | No | Often yes for E2E | | Runs local HTTP server | No (optional forward) | Yes (`:4242/webhook`) | Use **`lomi listen`** as the primary integration path. Use **`lomi dev`** when you only need a local receiver. Local development server Webhooks Initialize a project # Quickstart command Source: https://docs.lomi.africa/build/cli/quickstart-command Run lomi quickstart to verify CLI connectivity and get recommended next steps. *** title: 'Quickstart command' description: 'Run lomi quickstart to verify CLI connectivity and get recommended next steps.' --------------------------------------------------------------------------------------------- `lomi quickstart` is a non-destructive golden-path check. It verifies your CLI token and API connectivity, then prints the commands most teams run next. ## Usage ```bash filename="Terminal" lomi quickstart ``` Machine-readable output for scripts and agents: ```bash filename="Terminal" lomi quickstart --json ``` Skip API checks (offline or docs-only): ```bash filename="Terminal" lomi quickstart --skip-probe ``` ## What it checks Unless `--skip-probe` is set, quickstart runs: 1. `GET /`, API connectivity 2. `GET /me`, identity and environment 3. `GET /accounts/balance`, authenticated balance read ## JSON response shape ```json { "status": "ready", "profile": "default", "organization": "Your Org", "environment": "sandbox", "probe": { "passed": 3, "failed": 0 }, "next_steps": [ { "command": "lomi checkout create --amount 10000 ...", "description": "Create a sandbox test checkout session" } ] } ``` CLI quickstart Command reference # Balance and settlement Source: https://docs.lomi.africa/build/money/balance-and-settlement How merchant balances, fees, and availability work after a successful payment. *** title: Balance and settlement description: How merchant balances, fees, and availability work after a successful payment. ------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; Balances are updated when a transaction reaches **`completed`**. This page explains how balances, fees, and fund availability work after a successful payment. **`completed` is not available.** A Mobile Money payment that is still `pending` is not a failure. Confirm status with [Verify payments](/build/reliability/verify-payments). Use `GET /settlements` when you need the reconciliation object for what you can withdraw. In the **test** environment, completed payments credit your **dashboard test balance** only, they do not update your live withdrawable balance. See **[Sandbox payments](/start/sandbox-payments)**. ## When balances change * **Merchant-receivable balance** increases by the **net** amount of the transaction (gross minus platform fee). * Updates run only for **`completed`** transactions. * A **metadata flag** records that balances were already applied for that transaction, so retries cannot double-credit. ## Availability delay Each transaction stores an **`available_at`** timestamp computed from organization payment settings: * `NOW() + get_payment_availability_delay(organization, provider, payment_method)` * Example: some card configurations default to a **delayed** availability window; Mobile Money may be **immediate**. The dashboard may show **available** vs **pending/settling** based on this and related rules. ## Multi-currency settlement When you accept payments in multiple currencies, lomi. may convert amounts for fee and settlement accounting. Your dashboard balance is credited in your organization's settlement currency according to your product settings. Exact API fields for “available” vs “pending” balances are exposed in [Balances](/api/balances). ## Settlement periods (API) Funds become withdrawable when `available_at` passes. For accounting, group completed payments by **availability date** and currency: * **`GET /settlements`**: list periods with gross, fee, and net totals per `{currency}:{YYYY-MM-DD}` (UTC date of `available_at`). * **`GET /settlements/{id}/transactions`**: line items for one period; sums should match the period totals. `status` on each period is `available` when all underlying transactions are past `available_at`, otherwise `pending`. This is org-scoped reconciliation, not the same as provider payout batches from Paystack or Flutterwave. See [List settlement periods](/api/settlements/SettlementsController_findAll) and [List settlement transactions](/api/settlements/SettlementsController_findTransactions). ## Coupons and completion Coupon redemption counts typically advance when the transaction completes, not when the coupon is merely typed at checkout. ## Related pages * [Payment and payout lifecycle](/build/reliability/payment-lifecycle) * [List settlement periods](/api/settlements/SettlementsController_findAll) * [Payout lifecycle](/build/reliability/payment-lifecycle) * [Discount coupons](/build/billing/discount-coupons) # Disputes Source: https://docs.lomi.africa/build/money/disputes Read card payment disputes (`GET /disputes`) and react to `DISPUTE_*` webhooks. *** title: Disputes description: Read card payment disputes (`GET /disputes`) and react to `DISPUTE_*` webhooks. -------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; Disputes are created automatically when the card network sends `charge.dispute.*` events for **card** payments. Use the read API to list and inspect disputes for your organization, subscribe to merchant webhooks, or open **Settings → Support → Disputes** in the [dashboard](https://dashboard.lomi.africa). High chargeback rates can pause payouts. See [Pricing](/start/merchant-of-record/pricing) for published dispute fees. v1 covers **read access** and merchant webhooks (`DISPUTE_CREATED`, `DISPUTE_UPDATED`, `DISPUTE_CLOSED`). Evidence submission and MoMo disputes are not supported yet. Submit dispute evidence in your card processor dashboard until an in-product flow ships. Sandbox cannot open a dispute. There is no test token. Disputes appear from live card-network events on card payments. ## When to use this API * **Ops / support**: list open disputes and link them to customers and transactions. * **Automation**: on `DISPUTE_CREATED`, pause fulfillment, notify Slack, or open a ticket. * **Reconciliation**: match `stripe_dispute_id` and `transaction_id` to your internal orders. ## List disputes ```bash curl "https://api.lomi.africa/disputes?page=1&pageSize=50" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ### Query parameters | Parameter | Type | Description | | ----------- | ----------------------------------- | --------------------------------------- | | `page` | `number` | Page number (default `1`) | | `pageSize` | `number` | Page size (default `50`) | | `status` | `pending` \| `resolved` \| `closed` | Filter by lomi. dispute status | | `startDate` | ISO 8601 | Disputes created on or after this time | | `endDate` | ISO 8601 | Disputes created on or before this time | lomi. stores a simplified status (`pending`, `resolved`, `closed`). Card-network outcomes such as `won` or `lost` are reflected in `resolution_details` while status is often `resolved`. `DISPUTE_CLOSED` webhooks can fire when the network closes a dispute even if the stored status is `resolved`, not `closed`. ### Response fields (list item) | Field | Description | | --------------------------------------- | ------------------------------------------------------------ | | `dispute_id` | lomi. dispute ID | | `transaction_id` | Linked payment transaction | | `customer_id` | Customer who paid | | `amount`, `currency_code` | Disputed amount | | `fee_amount` | Dispute fee charged to your account, if any | | `reason` | Card-network dispute reason (e.g. `fraudulent`, `duplicate`) | | `status` | `pending`, `resolved`, or `closed` | | `stripe_dispute_id`, `stripe_charge_id` | Processor references | | `resolution_date`, `resolution_details` | Outcome when resolved (`won`, `lost`, etc.) | | `customer_name`, `customer_email` | Customer snapshot | ## Get a dispute ```bash curl "https://api.lomi.africa/disputes/{dispute_id}" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` The single-dispute response includes the same fields plus `evidence_details`, `transaction_gross_amount`, and `transaction_status`. ## Webhooks Subscribe to dispute events when [creating a webhook](/build/reliability). Recommended handler pattern: 1. Verify `X-Lomi-Signature` on the raw body. 2. On `DISPUTE_CREATED`, fetch authoritative state with `GET /disputes/{id}` if needed. 3. Treat events as idempotent (the card network may retry; lomi. deduplicates dispute creation). | Event | When it fires | | ----------------- | ------------------------------------------- | | `DISPUTE_CREATED` | Cardholder opened a dispute | | `DISPUTE_UPDATED` | Status or evidence changed on the processor | | `DISPUTE_CLOSED` | Processor sent `charge.dispute.closed` | Example payload shape (fields may include additional metadata): ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "DISPUTE_CREATED", "timestamp": "2026-06-19T12:00:00.000Z", "data": { "dispute_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "transaction_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "customer_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "amount": 15000, "currency_code": "XOF", "reason": "fraudulent", "status": "pending", "stripe_dispute_id": "dp_123", "stripe_charge_id": "ch_456", "organization_id": "org_789" } } ``` ## Dashboard In the merchant dashboard, go to **Settings → Support → Disputes** to browse chargebacks, open a dispute detail sheet, and jump to the linked transaction. Requires `transaction.read` on your organization role. ## Related * [Refunds](/build/money/refunds): voluntary refunds before or after a dispute * [Webhooks](/build/reliability): event types and verification * [Cards](/build/payment-methods/cards): card payments that can receive disputes # Money movement Source: https://docs.lomi.africa/build/money Transactions, refunds, disputes, Radar, payouts, and settlement. *** title: Money movement description: Transactions, refunds, disputes, Radar, payouts, and settlement. index: true ----------- Track money after a payment: the transaction record, refunds and disputes, fraud screening, payouts, and when funds become available. # Payouts Source: https://docs.lomi.africa/build/money/payouts Withdraw to your registered accounts or pay beneficiaries (`POST /payouts`). *** title: Payouts description: Withdraw to your registered accounts or pay beneficiaries (`POST /payouts`). ----------------------------------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; Use **`POST /payouts`** for all outbound money movement. * **`destination: "self"`**: withdraw to a **registered** payout method on your organization (`payout_method_id` required). Supports `bank`, `spi`, `wave`, and `mtn` (mobile money). * **`destination: "beneficiary"`**: pay a third party on **mobile rails** (`wave` or `mtn`). Requires `recipient.name` and `recipient.phone` (any E.164 mobile number; **not** tied to `payout_method_id`). Bank payouts to arbitrary accounts are not supported. **Wave and MTN payouts require a live API key** (`lomi_sk_live_…`). Test keys return `400` for mobile-money payouts, no real provider transfer is initiated. SPI settlement for self payouts continues asynchronously after the RPC succeeds. SPI beneficiary payouts are ledger-only until SPI execution ships. ## Create a payout ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live' }); // Withdraw to your registered Wave mobile money method await lomi.payouts.create({ destination: 'self', rail: 'wave', amount: 50000, currency_code: 'XOF', payout_method_id: '550e8400-e29b-41d4-a716-446655440000', }); // Pay a contractor via Wave await lomi.payouts.create({ destination: 'beneficiary', rail: 'wave', amount: 10000, currency_code: 'XOF', recipient: { name: 'Ada Lovelace', phone: '+221771234567' }, reason: 'Invoice #12', }); ``` ```bash curl -X POST "https://api.lomi.africa/payouts" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "destination": "beneficiary", "rail": "wave", "amount": 10000, "currency_code": "XOF", "recipient": { "name": "Ada Lovelace", "phone": "+221771234567" } }' ``` ## Payload reference | Field | Type | Required | | ------------------ | ------------------------------------------ | -------------------------------------------------- | | `destination` | `'self'` \| `'beneficiary'` | **Yes** | | `rail` | `'wave'` \| `'spi'` \| `'bank'` \| `'mtn'` | **Yes** | | `amount` | `number` | **Yes** | | `currency_code` | `string` | **Yes** | | `payout_method_id` | UUID | **Yes** for `self`; required for beneficiary `spi` | | `recipient` | `{ name, phone }` | **Yes** for beneficiary `wave` | | `reason` | `string` | No | ## List and get * `GET /payouts`, withdrawals and beneficiary payouts (each row includes `kind`). Test API keys list **test withdrawals only**; live beneficiary payout rows are omitted. * `GET /payouts/{id}`, resolves merchant withdrawals and beneficiary payouts by ID (org-scoped). ## Payout blocked by platform risk controls Large or high-velocity withdrawals may be held automatically. If `POST /payouts` fails with a risk-related error, wait and try again later or [contact support](/start/support). For **incoming payment** risk screening (card and mobile money charges), see [lomi. Radar](/build/money/radar). # lomi. Radar Source: https://docs.lomi.africa/build/money/radar Screen incoming card and mobile-money charges with risk assessments, webhooks, and metered billing. *** title: lomi. Radar description: Screen incoming card and mobile-money charges with risk assessments, webhooks, and metered billing. ---------------------------------------------------------------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; **lomi. Radar** evaluates **incoming** charges (card, MTN, Wave) before they complete. It is separate from [Disputes](/build/money/disputes) (card chargebacks after the fact) and from platform payout controls described in [Payouts](/build/money/payouts). Radar is **opt-in** per organization. Enable it with `PATCH /organizations/radar-settings` (`enabled: true`). Default is off for existing merchants. ## Enable Radar ```bash curl -X PATCH "https://api.lomi.africa/organizations/radar-settings" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "enabled": true, "mode": "block" }' ``` | Field | Values | Meaning | | -------------------------- | ---------------- | -------------------------------------------------- | | `enabled` | `true` / `false` | Turn screening on or off | | `mode` | `monitor` | Flag risky charges but allow them | | `mode` | `block` | Reject charges that trigger block rules | | `stripe_radar_passthrough` | `true` (default) | Merge card-network risk signals after card capture | ## Built-in rules (v1) | Rule | Rails | Default action | | ----------------------- | --------------- | ------------------------------------ | | `high_value_charge` | card, MTN, Wave | Flag ≥ 500,000 XOF | | `velocity_charges_1h` | all | Block > 20 charges / customer / hour | | `repeat_phone_24h` | MTN, Wave | Flag same phone across customers | | `card_country_mismatch` | card | Flag billing country outside WAEMU | ## List risk assessments ```bash curl "https://api.lomi.africa/risk-assessments?decision=flag&page=1&pageSize=50" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` Optional filters: `decision` (`allow`, `flag`, `block`), `rail` (`card`, `mtn`, `wave`), `startDate`, `endDate`. Example row: ```json { "assessment_id": "…", "transaction_id": "…", "rail": "mtn", "decision": "flag", "risk_score": 45, "signals": [{ "rule": "repeat_phone_24h", "action": "flag", "detail": "…" }], "amount": 10000, "currency_code": "XOF" } ``` ## Webhooks Subscribe to: * `PAYMENT_RISK_FLAGGED`, charge allowed but flagged for review * `PAYMENT_RISK_BLOCKED`, charge blocked when `mode` is `block` Payload includes `assessment_id`, `transaction_id`, `rail`, `decision`, `risk_score`, `signals`, `amount`, and `currency_code`. ## Disputes vs Radar vs payout controls | | **Disputes** | **Radar** | **Payout controls** | | --------- | ----------------------------- | ----------------------- | ------------------------ | | Timing | After chargeback | Before/during charge | At withdrawal | | API | `GET /disputes` | `GET /risk-assessments` | None (internal) | | Dashboard | Settings → Support → Disputes | Transactions risk badge | Withdrawal error message | ## Metering When Radar is enabled and a `radar_screen` meter is configured, each screening emits a usage event billed through [Usage billing](/build/billing/usage-billing). # Refunds Source: https://docs.lomi.africa/build/money/refunds Refund completed transactions (`POST /refunds`). *** title: Refunds description: Refund completed transactions (`POST /refunds`). ------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; Use **`POST /refunds`** to refund a **completed** transaction. Supported providers are **card**, **Wave**, and **MTN** (same flows as the dashboard). Your **balance is updated immediately**; customer credit timing depends on the payment type. **MTN (live):** the original payment must have a provider reference (`provider_checkout_id` from RequestToPay). Refunds use the MTN Disbursement refund API and are asynchronous on MTN's side; lomi. polls until completion. **Test mode** refunds are ledger-only (no MTN API call). List and retrieve refunds with **`GET /refunds`** and **`GET /refunds/{id}`**. Card refunds are recorded immediately on your account. The customer's bank or card issuer typically posts the credit within several business days. lomi. Network Operators can add `Lomi-Account: acct_...` to refund a connected Member Account transaction when the membership has `refund.create`. ## Create a refund ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live', }); const refund = await lomi.refunds.create({ transaction_id: '123e4567-e89b-12d3-a456-426614174000', amount: 5000, reason: 'duplicate_charge', refund_type: 'partial', // optional: 'full' | 'partial' }); ``` ```python import requests, os r = requests.post( "https://api.lomi.africa/refunds", headers={"X-API-KEY": os.environ["LOMI_SECRET_KEY"], "Content-Type": "application/json"}, json={ "transaction_id": "123e4567-e89b-12d3-a456-426614174000", "amount": 5000, "reason": "duplicate_charge", }, ) print(r.status_code, r.json()) ``` ```bash curl -X POST "https://api.lomi.africa/refunds" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "transaction_id": "123e4567-e89b-12d3-a456-426614174000", "amount": 5000, "reason": "duplicate_charge" }' ``` Network refund: ```bash curl -X POST "https://api.lomi.africa/refunds" \ -H "X-API-KEY: $LOMI_OPERATOR_API_KEY" \ -H "Lomi-Account: acct_1234567890" \ -H "Content-Type: application/json" \ -d '{ "transaction_id": "123e4567-e89b-12d3-a456-426614174000", "amount": 5000, "reason": "duplicate_charge" }' ``` ## Payload reference | Field | Type | Required | | ---------------- | ----------------------- | ---------------------------------------------------------- | | `transaction_id` | `string` (UUID) | **Yes** | | `amount` | `number` | **Yes** | | `reason` | `string` | No | | `refund_type` | `'full'` \| `'partial'` | No, inferred from `amount` vs transaction gross if omitted | ## List refunds `GET /refunds?status=completed&limit=50&offset=0` ## Get a refund `GET /refunds/{refund_id}` # Transactions Source: https://docs.lomi.africa/build/money/transactions Retrieve transaction history and details with advanced filtering. *** title: Transactions description: Retrieve transaction history and details with advanced filtering. ------------------------------------------------------------------------------ import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; The Transactions API provides read-only access to transaction history for your organization. Transactions are automatically created when payments are processed through Checkout sessions or Payment links. **Behavior:** how statuses transition, when balances credit, and how expiration works, see **[Payment and payout lifecycle](/build/reliability/payment-lifecycle)** and **[Balance and settlement](/build/money/balance-and-settlement)**. lomi. Network Operators can add `Lomi-Account: acct_...` to read transactions for a connected Member Account when the membership has `transaction.read`. ## List transactions Retrieve all transactions with advanced filtering options. ### Query Parameters | Parameter | Type | Description | | --------------- | --------- | -------------------------------------------------------------------------- | | `provider` | `string` | Filter by payment provider code (example: `WAVE`, cards channel code) | | `status` | `string` | Filter by status. Comma-separated for multiple (e.g., `completed,pending`) | | `type` | `string` | Filter by transaction type. Comma-separated (e.g., `payment,refund`) | | `currency` | `string` | Filter by currency code. Comma-separated (e.g., `XOF,USD`) | | `paymentMethod` | `string` | Filter by payment method. Comma-separated (e.g., `MOBILE_MONEY,CARDS`) | | `startDate` | `string` | Filter from this date (ISO 8601 format) | | `endDate` | `string` | Filter up to this date (ISO 8601 format) | | `isPos` | `boolean` | Filter POS transactions only | | `page` | `number` | Page number (default: `1`) | | `pageSize` | `number` | Items per page (default: `50`) | ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live', }); // List with filters const transactions = await lomi.transactions.list({ status: 'completed', provider: 'WAVE', startDate: '2024-01-01T00:00:00Z', endDate: '2024-12-31T23:59:59Z', page: 1, pageSize: 50, }); console.log(`Found ${transactions.length} transactions`); ``` ```python from lomi import LomiClient import os client = LomiClient( api_key=os.environ["LOMI_SECRET_KEY"], environment="test" ) # List with filters transactions = client.transactions.list( status="completed", provider="WAVE", startDate="2024-01-01T00:00:00Z", endDate="2024-12-31T23:59:59Z", page=1, pageSize=50 ) print(f"Found {len(transactions)} transactions") ``` ```bash curl -X GET "https://api.lomi.africa/transactions?status=completed&provider=WAVE&startDate=2024-01-01T00:00:00Z&page=1&pageSize=50" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` Network transaction read: ```bash curl -X GET "https://api.lomi.africa/transactions?status=completed" \ -H "X-API-KEY: $LOMI_OPERATOR_API_KEY" \ -H "Lomi-Account: acct_1234567890" ``` ### Response ```json [ { "transaction_id": "tx_abc123...", "organization_id": "org_xyz789...", "customer_id": "cus_def456...", "gross_amount": 10000, "net_amount": 9700, "fee_amount": 300, "currency_code": "XOF", "status": "completed", "type": "payment", "provider_code": "WAVE", "payment_method_code": "MOBILE_MONEY", "description": "Payment for Order #12345", "metadata": { "order_id": "ORD-12345" }, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:31:00Z" } ] ``` *** ## Get a transaction Retrieve details of a specific transaction by ID. ```typescript const tx = await lomi.transactions.get('tx_abc123...'); console.log(`Amount: ${tx.gross_amount} ${tx.currency_code}`); console.log(`Status: ${tx.status}`); ``` ```python tx = client.transactions.get('tx_abc123...') print(f"Amount: {tx['gross_amount']} {tx['currency_code']}") print(f"Status: {tx['status']}") ``` ```bash curl -X GET "https://api.lomi.africa/transactions/tx_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Transaction Object | Field | Type | Description | | --------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transaction_id` | `string` | Unique transaction identifier | | `organization_id` | `string` | Organization that owns this transaction | | `customer_id` | `string` | Associated customer (if any) | | `gross_amount` | `number` | Total amount charged | | `net_amount` | `number` | Amount after fees | | `fee_amount` | `number` | Total fees deducted | | `currency_code` | `string` | Currency (e.g., `XOF`, `USD`) | | `status` | `string` | `pending`, `completed`, `held`, `failed`, `refunded`, `expired`. `held` is a platform risk hold: the charge succeeded but the funds are not withdrawable. Merchants see this as under review. | | `type` | `string` | `payment`, `refund`, `payout` | | `provider_code` | `string` | Payment provider (e.g., `WAVE`) | | `payment_method_code` | `string` | Payment method (e.g., `MOBILE_MONEY`, `CARDS`) | | `description` | `string` | Transaction description | | `metadata` | `object` | Custom metadata passed during payment | | `created_at` | `string` | ISO 8601 timestamp | | `updated_at` | `string` | ISO 8601 timestamp | *** ## Error Responses | Status | Description | | ------ | -------------------------------------- | | `401` | Invalid or missing API key | | `404` | Transaction not found or access denied | | `429` | Rate limit exceeded | # How do I accept cards? Source: https://docs.lomi.africa/build/payment-methods/cards Choose hosted checkout for most card flows, or embedded card collection when your app needs full UI control. *** title: 'How do I accept cards?' description: 'Choose hosted checkout for most card flows, or embedded card collection when your app needs full UI control.' docType: how-to --------------- import { Callout } from '@/components/docs/docs-callout'; import { DocsScreenshot } from '@/components/docs/docs-screenshot'; import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; For most teams, card payments should start with hosted checkout. Use embedded card collection only when your product needs card entry inside your own UI. ## At a glance | | | | ---------------- | -------------------------------------------------------- | | Approval | Hosted 3DS when the issuer requires it | | Settlement | After `completed`; some card setups delay `available_at` | | Direct vs hosted | Hosted. `POST /charge/card` returns `503` | | Refund | Completed card transactions | | Min / max | - | Same columns as [Payment channels](/build/payment-channels#channel-capabilities). Cards work on **hosted checkout** (fastest) or **embedded** flows with [Payment Elements](/build/accept/payment-elements) and `POST /charge/card`. ## Which card flow should I use? | Flow | Use when | | -------------------- | ---------------------------------------------------------- | | Hosted checkout | You want the fastest, safest card experience | | Payment links | You want a card-capable shareable URL | | Embedded card charge | You own the checkout UI and can handle client confirmation | | Payment Elements | You want reusable card UI primitives in your application | ## Hosted checkout (recommended) 1. Create a checkout session. 2. Customer selects **Card** and enters details on the lomi. page. 3. Handle 3D Secure when the test card or issuer requires authentication. 4. Verify with webhooks before fulfilling. ## Embedded card charge Embedded card charges (`POST /charge/card`) and Switch charges (`POST /charge/switch`) are not available yet and currently return `503 service_unavailable`. Use [hosted checkout](/build/accept/checkout) for cards today. 1. `POST /charge/card` → receive `client_secret`. 2. Confirm with Payment Elements on your site. 3. Poll or webhook for final `transaction_status`. See [Charges API](/api/charge/ChargesController_createCardCharge) and the [direct charge reference app](/build/accept/direct-charges#reference-implementation). ## Sandbox test cards | PAN | Behavior | | --------------------------- | --------------------------------------------------------------------- | | `4242 4242 4242 4242` | Success | | Decline / auth test numbers | See [Sandbox payments](/start/sandbox-payments#testing-card-payments) | Never use real card numbers in test mode or test numbers in live mode. ## Safety notes * Never handle secret keys in a browser or mobile app. * Treat `client_secret` values as short-lived sensitive capabilities. * Use publishable keys only where the card flow requires them. * Confirm final state from webhooks or server-side API reads. Hosted checkout Payment Elements Verify payments Sandbox payments # How do I accept MTN? Source: https://docs.lomi.africa/build/payment-methods/mtn-momo Accept MTN in Côte d'Ivoire and regional markets, hosted checkout, POST /charge/mtn, and country codes. *** title: 'How do I accept MTN?' description: "Accept MTN in Côte d'Ivoire and regional markets, hosted checkout, POST /charge/mtn, and country codes." docType: how-to --------------- import { Callout } from '@/components/docs/docs-callout'; import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; MTN supports multiple countries beyond UEMOA. Use [hosted checkout](/build/accept/checkout) unless you need `POST /charge/mtn`. ## At a glance | | | | ---------------- | ------------------------------------------------------------------------------------------- | | Approval | Push / PIN on the customer's phone (`next_action: await_webhook`) | | Settlement | Test: often immediate `completed`. Live: stays `PENDING` until approve, then `available_at` | | Direct vs hosted | Hosted and `POST /charge/mtn` | | Refund | Completed MTN transactions (live refunds are async) | | Min / max | - | Same columns as [Payment channels](/build/payment-channels#channel-capabilities). ## Direct charge essentials * Pass `phone_number` in E.164 format (e.g. `+2250700000000`). * Pass `countryCode` (ISO 3166-1 alpha-2) when the payer is outside CI-default is `CI`. * **Live:** response status is `PENDING` until the customer approves on their phone. * **Test:** with a test API key, status may be `completed` immediately. See [Charges API: MTN](/api/charge/ChargesController_createMtnCharge). ## Supported countries Full dial-prefix and MTN environment table: [Payment channels](/build/payment-channels#mtn-momo-supported-countries). Sandbox MSISDN patterns and test behavior: [Sandbox payments: MTN](/start/sandbox-payments). ## Verify before fulfill Live MTN flows are **asynchronous**. Poll `GET /transactions/{id}` or use webhooks; see [Verify payments](/build/reliability/verify-payments). ## Refunds Live MTN refunds require the original payment’s provider reference and may use the Disbursement API asynchronously. Test refunds are ledger-only. See [Refunds](/build/money/refunds). Mobile money Direct charges MTN developer portal # What is SPI? Source: https://docs.lomi.africa/build/payment-methods/spi SPI bank and mobile-money network coverage and hosted checkout in West Africa. *** title: 'What is SPI?' description: 'SPI bank and mobile-money network coverage and hosted checkout in West Africa.' docType: explanation -------------------- import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; **SPI** (Système de Paiement Interopérable) connects mobile-money operators and banks across West Africa. On lomi., SPI-backed methods appear on **hosted checkout** alongside Wave, MTN, and cards. ## At a glance | | | | ---------------- | ------------------------------------------------------- | | Approval | Hosted: operator, USSD, or app on the checkout page | | Settlement | After the hosted payment completes, then `available_at` | | Direct vs hosted | Hosted only | | Refund | Not listed on `POST /refunds` | | Min / max | - | Same columns as [Payment channels](/build/payment-channels#channel-capabilities). ## Integration path Use [hosted checkout](/build/accept/checkout) or [payment links](/build/accept/payment-links). SPI-specific UX (operator selection, USSD, app redirect) is handled on the hosted page. ## Payouts Some payout rails use SPI for bank or mobile-money disbursements. See [Payouts](/build/money/payouts) and [Sandbox payouts](/start/sandbox-payments#payouts-in-test-mode). Payment channels Mobile money Providers API # How do I accept Wave? Source: https://docs.lomi.africa/build/payment-methods/wave Wave mobile money via hosted checkout or POST /charge/wave, with async live behavior and XOF requirements. *** title: 'How do I accept Wave?' description: 'Wave mobile money via hosted checkout or POST /charge/wave, with async live behavior and XOF requirements.' docType: how-to --------------- import { Callout } from '@/components/docs/docs-callout'; import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; Wave is the primary mobile-money rail for **UEMOA (XOF)** merchants. Most teams should use [hosted checkout](/build/accept/checkout) or [payment links](/build/accept/payment-links) first. ## At a glance | | | | ---------------- | ------------------------------------------------------------------------------------------------------ | | Approval | Redirect or launch URL (`wave_launch_url` / `checkout_url`) | | Settlement | Test: often when the charge is created. Live: after the customer approves in Wave, then `available_at` | | Direct vs hosted | Hosted and `POST /charge/wave` | | Refund | Completed Wave transactions | | Min / max | - | | Currency | `XOF` | Same columns as [Payment channels](/build/payment-channels#channel-capabilities). ## Hosted checkout (recommended) 1. Create a checkout session with `currency_code: "XOF"`. 2. Customer selects **Wave** on the hosted page. 3. Customer approves in the Wave app. 4. Confirm with webhooks or `GET /transactions/{id}` before fulfilling. ## Direct charge `POST /charge/wave` requires **XOF** and a nested `customer` object with `name` and `phoneNumber` in E.164 format. The response includes `wave_launch_url` or `checkout_url`: redirect or deep-link the customer. Wave must be connected in the dashboard with an **Aggregated Merchant ID**. If it is missing, the API returns `400` with `Wave provider not configured for this organization (missing Aggregated Merchant ID)`. ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/charge/wave" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "currency": "XOF", "customer": { "name": "Jane Doe", "email": "customer@example.com", "phoneNumber": "+2250707070707" }, "successUrl": "https://example.com/success", "errorUrl": "https://example.com/error" }' ``` See [Charges API](/api/charge/ChargesController_createWaveCharge) and [Direct charges](/build/accept/direct-charges). ## Test vs live | | Test | Live | | -------------- | ------------------------------------ | ------------------------------- | | Balance credit | Often when session/charge is created | After customer approves in Wave | | Status | Usually `completed` quickly | Starts `pending` | In live mode, never fulfill on redirect alone. See [Verify payments](/build/reliability/verify-payments). ## Payouts Wave **beneficiary** payouts require a live key and valid recipient phone. Wave self-withdrawals are **live-only**-test keys return `400` for Wave payout rails. See [Sandbox payouts](/start/sandbox-payments#payouts-in-test-mode). Payment channels Mobile money Sandbox payments # Customers Source: https://docs.lomi.africa/build/platform/customers Manage customer profiles and track their payment history. *** title: Customers description: Manage customer profiles and track their payment history. ---------------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; The Customers API allows you to create, retrieve, update, and manage customer profiles associated with your merchant account. ## Create a customer **API reference (full request schema):** [Create customer](/api/customers/CustomersController_create) **Essentials:** `name` is required. Pass `email`, `phone_number`, `whatsapp_number`, address fields, `is_business`, and `metadata` as needed for checkout prefill and your CRM. ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live', }); const customer = await lomi.customers.create({ name: 'Amadou Ba', email: 'amadou.ba@example.com', phone_number: '+221771234567', country: 'Senegal', city: 'Dakar', is_business: false, metadata: { internal_id: 'USER_123', }, }); console.log(`Customer created: ${customer.id}`); ``` ```python from lomi import LomiClient import os client = LomiClient( api_key=os.environ["LOMI_SECRET_KEY"], environment="test" ) customer = client.customers.create({ "name": "Amadou Ba", "email": "amadou.ba@example.com", "phone_number": "+221771234567", "country": "Senegal", "city": "Dakar", "is_business": False, "metadata": {"internal_id": "USER_123"} }) print(f"Customer created: {customer['id']}") ``` ```bash curl -X POST "https://api.lomi.africa/customers" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Amadou Ba", "email": "amadou.ba@example.com", "phone_number": "+221771234567", "country": "Senegal", "city": "Dakar", "is_business": false, "metadata": {"internal_id": "USER_123"} }' ``` *** ## List customers **API reference:** [List customers](/api/customers/CustomersController_findAll), `search`, `type`, `status`, `page`, and `pageSize`. ```typescript const customers = await lomi.customers.list({ search: 'amadou', type: 'individual', status: 'active', page: 1, pageSize: 20, }); ``` ```python customers = client.customers.list( search="amadou", type="individual", status="active", page=1, pageSize=20 ) ``` ```bash curl -X GET "https://api.lomi.africa/customers?search=amadou&type=individual&page=1&pageSize=20" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ### Response ```json { "customers": [...], "pagination": { "page": 1, "pageSize": 20, "totalCount": 150, "totalPages": 8 } } ``` *** ## Get a customer **API reference:** [Get customer](/api/customers/CustomersController_findOne) ```typescript const customer = await lomi.customers.get('cus_abc123...'); ``` ```python customer = client.customers.get('cus_abc123...') ``` ```bash curl -X GET "https://api.lomi.africa/customers/cus_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Update a customer **API reference:** [Update customer](/api/customers/CustomersController_update), all body fields are optional. ```typescript const updated = await lomi.customers.update('cus_abc123...', { email: 'new.email@example.com', metadata: { vip: true }, }); ``` ```python updated = client.customers.update('cus_abc123...', { "email": "new.email@example.com", "metadata": {"vip": True} }) ``` ```bash curl -X PATCH "https://api.lomi.africa/customers/cus_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "new.email@example.com"}' ``` *** ## Delete a customer **API reference:** [Delete customer](/api/customers/CustomersController_remove) ```typescript await lomi.customers.delete('cus_abc123...'); ``` ```python client.customers.delete('cus_abc123...') ``` ```bash curl -X DELETE "https://api.lomi.africa/customers/cus_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Get customer transactions **API reference:** [Customer transactions](/api/customers/CustomersController_getTransactions) ```typescript const transactions = await lomi.customers.getTransactions('cus_abc123...'); transactions.forEach(tx => { console.log(`${tx.description}: ${tx.gross_amount} ${tx.currency_code}`); }); ``` ```python transactions = client.customers.get_transactions('cus_abc123...') for tx in transactions: print(f"{tx['description']}: {tx['gross_amount']} {tx['currency_code']}") ``` ```bash curl -X GET "https://api.lomi.africa/customers/cus_abc123.../transactions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ### Response ```json [ { "transaction_id": "tx_def456...", "description": "Payment for Product A", "gross_amount": 10000, "currency_code": "XOF", "status": "completed", "created_at": "2024-01-15T10:30:00Z" } ] ``` *** ## Create customer portal launch session **API reference:** [Portal launch session](/api/customers/CustomersController_createPortalSession) Generate a one-time hosted customer portal URL from your backend. Returns `launch_url` for redirect. See [Customer portal](/build/billing/customer-portal) for flows, security, and production setup. ```typescript import axios from 'axios'; const { data: launch } = await axios.post( 'https://api.lomi.africa/customers/cus_abc123.../portal', { return_url: 'https://merchant.example.com/account', flow_type: 'subscription_cancel', flow_subscription_id: 'sub_abc123...', flow_after_completion_url: 'https://merchant.example.com/account/subscription-cancelled', }, { headers: { 'X-API-KEY': process.env.LOMI_SECRET_KEY!, 'Content-Type': 'application/json', }, }, ); window.location.href = launch.launch_url; ``` ```bash curl -X POST "https://api.lomi.africa/customers/cus_abc123.../portal" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "return_url": "https://merchant.example.com/account", "flow_type": "subscription_cancel", "flow_subscription_id": "sub_abc123...", "flow_after_completion_url": "https://merchant.example.com/account/subscription-cancelled" }' ``` *** ## Related API * [Create customer](/api/customers/CustomersController_create) * [List customers](/api/customers/CustomersController_findAll) * [Get customer](/api/customers/CustomersController_findOne) * [Update customer](/api/customers/CustomersController_update) * [Delete customer](/api/customers/CustomersController_remove) * [Customer transactions](/api/customers/CustomersController_getTransactions) * [Portal launch session](/api/customers/CustomersController_createPortalSession) * [Customer portal guide](/build/billing/customer-portal) # Merchants Source: https://docs.lomi.africa/build/platform/merchants The Merchants API allows you to retrieve information about merchant accounts, including details, recurring revenue metrics, and balances. *** title: 'Merchants' description: 'The Merchants API allows you to retrieve information about merchant accounts, including details, recurring revenue metrics, and balances.' -------------------------------------------------------------------------------------------------------------------------------------------------------- ## Authentication Requests require authentication using your API key in the `` `X-API-Key` `` header. See the [Authentication](/start/api-keys) guide. For organization-level settings, pricing, and metrics, prefer the [Organizations](/build/platform/organizations) API when your integration key is org-scoped. ## Endpoints ### Get merchant details Retrieves detailed information about a specific merchant account. **Endpoint:** `` `GET /merchants/{id}` `` **Path parameters:** | Parameter | Type | Required | Description | | ---------- | -------------- | -------- | -------------------------------------- | | `` `id` `` | `` `string` `` | Yes | The unique identifier of the merchant. | **Example response (200 OK):** ```json filename="Example response for GET /merchants/{id}" { "data": { "merchant_id": "904d003c-3736-41d4-90a5-9de74d404fd7", "name": "Test Merchant", "email": "merchant@example.com", "phone_number": "+123456789", "country": "SN", "mrr": 50000, // In smallest currency unit "arr": 600000, // In smallest currency unit "merchant_lifetime_value": 14250, // Predicted per-customer LTV in org default currency "retry_payment_every": 3, "total_retries": 5, "metadata": { "industry": "e-commerce" }, "created_at": "2023-01-15T10:30:00Z", "updated_at": "2023-02-20T14:45:00Z" } } ``` *(See [Data Models](/api/data-models#merchant-object) for property descriptions)* **Possible error responses:** | Status Code | Error Code | Description | | ----------- | -------------------------- | -------------------------------------------- | | `` `401` `` | `` `UNAUTHORIZED` `` | Authentication failed or API key is invalid. | | `` `404` `` | `` `MERCHANT_NOT_FOUND` `` | No merchant found with the provided ID. | | `` `500` `` | `` `DATABASE_ERROR` `` | Error retrieving merchant details. | | `` `500` `` | `` `INTERNAL_ERROR` `` | Internal server error. | ### Get merchant monthly recurring revenue (MRR) Retrieves the current MRR for a merchant. **Endpoint:** `` `GET /merchants/{id}/mrr` `` **Path parameters:** | Parameter | Type | Required | Description | | ---------- | -------------- | -------- | -------------------------------------- | | `` `id` `` | `` `string` `` | Yes | The unique identifier of the merchant. | **Example response (200 OK):** ```json filename="Example response for GET /merchants/{id}/mrr" { "data": { "merchant_id": "904d003c-3736-41d4-90a5-9de74d404fd7", "mrr": 50000, // In smallest currency unit "currency_code": "XOF", "as_of_date": "2023-04-01T00:00:00Z" } } ``` **Possible error responses:** | Status Code | Error Code | Description | | ----------- | -------------------------- | -------------------------------------------- | | `` `401` `` | `` `UNAUTHORIZED` `` | Authentication failed or API key is invalid. | | `` `404` `` | `` `MERCHANT_NOT_FOUND` `` | No merchant found with the provided ID. | | `` `404` `` | `` `NOT_FOUND` `` | No MRR data found for the merchant. | | `` `500` `` | `` `DATABASE_ERROR` `` | Error retrieving merchant MRR. | | `` `500` `` | `` `INTERNAL_ERROR` `` | Internal server error. | ### Get merchant annual recurring revenue (ARR) Retrieves the current ARR for a merchant. **Endpoint:** `` `GET /merchants/{id}/arr` `` **Path parameters:** | Parameter | Type | Required | Description | | ---------- | -------------- | -------- | -------------------------------------- | | `` `id` `` | `` `string` `` | Yes | The unique identifier of the merchant. | **Example response (200 OK):** ```json filename="Example response for GET /merchants/{id}/arr" { "data": { "merchant_id": "904d003c-3736-41d4-90a5-9de74d404fd7", "arr": 600000, // In smallest currency unit "currency_code": "XOF", "as_of_date": "2023-04-01T00:00:00Z" } } ``` **Possible error responses:** | Status Code | Error Code | Description | | ----------- | -------------------------- | -------------------------------------------- | | `` `401` `` | `` `UNAUTHORIZED` `` | Authentication failed or API key is invalid. | | `` `404` `` | `` `MERCHANT_NOT_FOUND` `` | No merchant found with the provided ID. | | `` `404` `` | `` `NOT_FOUND` `` | No ARR data found for the merchant. | | `` `500` `` | `` `DATABASE_ERROR` `` | Error retrieving merchant ARR. | | `` `500` `` | `` `INTERNAL_ERROR` `` | Internal server error. | ### Get merchant account balance Retrieves the current account balance for a merchant in a specific currency. **Endpoint:** `` `GET /merchants/{id}/balance` `` **Path parameters:** | Parameter | Type | Required | Description | | ---------- | -------------- | -------- | -------------------------------------- | | `` `id` `` | `` `string` `` | Yes | The unique identifier of the merchant. | **Query parameters:** | Parameter | Type | Required | Description | | --------------------- | -------------- | -------- | --------------------------------------------------------------- | | `` `currency_code` `` | `` `string` `` | Yes | Currency code for the balance (e.g., `` `XOF` ``, `` `USD` ``). | **Example response (200 OK):** ```json filename="Example response for GET /merchants/{id}/balance" { "data": { "merchant_id": "904d003c-3736-41d4-90a5-9de74d404fd7", "currency_code": "XOF", "balance": 250000, // In smallest currency unit "as_of_date": "2023-04-01T12:30:45Z" } } ``` **Possible error responses:** | Status Code | Error Code | Description | | ----------- | ------------------------- | ----------------------------------------------------- | | `` `400` `` | `` `MISSING_PARAMETER` `` | The `` `currency_code` `` query parameter is missing. | | `` `401` `` | `` `UNAUTHORIZED` `` | Authentication failed or API key is invalid. | | `` `500` `` | `` `DATABASE_ERROR` `` | Error retrieving merchant balance. | | `` `500` `` | `` `INTERNAL_ERROR` `` | Internal server error. | ## Implementation notes * **Organization scoping:** Merchant details, MRR, and ARR are always returned for the **organization bound to your API key**. If a merchant belongs to multiple organizations, you only see metrics for the org your key is linked to — never cross-org data. * Cached organization metrics (`` `mrr` ``, `` `arr` ``, `` `merchant_lifetime_value` ``, transaction and customer counts) are **refreshed daily at 03:00 UTC** for live data, and **again when subscriptions change**. `` `as_of_date` `` on MRR/ARR responses reflects the last refresh; balance `` `as_of_date` `` is the account `` `updated_at` ``. * `` `mrr` `` / `` `arr` `` are the linked organization's Monthly / Annual Recurring Revenue from active subscriptions, in the org default currency. * `` `merchant_lifetime_value` `` is a **predicted per-customer LTV** for the linked organization (live environment): Customer Value (net revenue per paying customer) multiplied by Average Lifespan (derived from repeat purchase behavior, capped at 5x). Refreshed with other cached metrics. This is **not** platform profit from the merchant (admin-only). * All monetary values are returned in the smallest currency unit (e.g., cents for USD, XOF represents the base unit directly). * Dates and times are returned in ISO 8601 format (`` `YYYY-MM-DDTHH:mm:ssZ` ``). * See the [Errors](/api/errors) guide for general error handling information. # lomi. Network Source: https://docs.lomi.africa/build/platform/network Run a marketplace or a SaaS platform on lomi.: onboard Member Accounts, charge on their behalf, keep your fee, and move money with transfers. *** title: 'lomi. Network' description: 'Run a marketplace or a SaaS platform on lomi.: onboard Member Accounts, charge on their behalf, keep your fee, and move money with transfers.' ------------------------------------------------------------------------------------------------------------------------------------------------------------ import { Callout } from '@/components/docs/docs-callout'; lomi. Network lets an **Operator** organization (a platform or a marketplace) accept payments on behalf of connected **Member Accounts**. A Member Account is a real lomi. organization with a public account id such as `acct_1a2b3c4d5e6f7g8h`. You decide who is the merchant of record, when the member is paid, and what you keep on each payment. Network uses the normal merchant API. Delegated requests add `Lomi-Account: acct_...` next to your Operator **secret** key. Transfers use the Operator key alone. Everything works in test mode as soon as you finish the setup wizard in the dashboard; live requires lomi. approval (see [Go live](#go-live)). | Term | Meaning | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Operator | The organization that owns the API key, configures fees, and initiates delegated requests and transfers. | | Member Account | The connected organization that receives the payment, checkout session, transaction, refund, or transfer. | | `acct_...` | Public Network account id for a Member Account. Operators use it in `Lomi-Account`, `transfer_data.destination`, and `POST /transfers`. | | Membership | The relationship between one Operator and one Member Account, per environment (test or live). | | Capability | Permission granted per membership and environment, such as `payment.create` or `transfer.receive`. | | Application fee | What you keep on a payment. Set by fee rules or per request with `application_fee_amount`. | | Transfer | A balance movement between the Operator and a Member Account (`tr_...`). | ## Design your integration ### Marketplace or SaaS platform Two shapes cover most platforms: * **Marketplace.** Buyers pay you for goods or services that your sellers deliver. You are the face of the checkout, you keep a fee, and the seller is paid the rest. Sellers are Member Accounts and usually never talk to lomi. directly. * **SaaS platform.** Your customers run their own business inside your product and accept payments from their own customers. Each customer is a Member Account, the merchant of record, and their own branding shows at checkout. You keep a per-payment fee. You can mix both on the same Network: the charge type is chosen per request, not per Operator. ### Pick a charge type | | Direct | Destination | Separate charges and transfers | | ------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------- | | How | `Lomi-Account: acct_...` on `POST /checkout-sessions` or `POST /charge/*` | `transfer_data: { destination }` on your own account | `transfer_group` on your own account, then `POST /transfers` later | | Merchant of record | Member | Operator | Operator | | Checkout branding and payment methods | Member | Operator | Operator | | Who is credited at completion | Member (net), your fee moves member → operator | Operator, then payment minus your fee is transferred to the member | Operator. Nothing moves until you transfer | | When the member is paid | Immediately | Immediately | When you call `POST /transfers` | | One payment, several members | No | No (one destination) | Yes (several transfers, same `transfer_group`) | | Refund source | Member balance (fee moves back) | Operator balance, member transfer pulled back | Operator balance, member transfers pulled back | | Best for | SaaS platforms, your customers accept payments in your product | Marketplaces with one seller per order | Marketplaces with carts across sellers, delayed payout, escrow-like flows | Rule of thumb: if the buyer should see the member's name and payment methods, use **direct**. If the buyer should see your brand, use **destination**, and switch to **separate charges and transfers** when one payment funds several members or you pay members later. ### Who pays lomi. processing fees Your platform profile (Network → Settings) has two switches that apply to every charge type: * `fees_collector`: `member` or `operator`. Who absorbs the lomi. processing fee on each payment. * `losses_collector`: `member` or `operator`. Who covers a refund when the balance that should pay it is short. See [Fees and settlement](#fees-and-settlement) for what moves in each case. ## Onboarding members ### Hosted invite 1. Open **Network → Members** in the [dashboard](https://dashboard.lomi.africa) and create an invite. Pick the capabilities to request and, optionally, the fee rule for this member. 2. Copy the hosted onboarding link, `https://dashboard.lomi.africa/network/enroll/{token}`, and send it to the member. 3. The member completes the hosted flow (below). The membership appears in **Members** as **In review** until verification clears, then **Enabled**. 4. Note the member public id (`acct_...`). The enrollment link opens a branded flow with your Operator name and logo. It asks for, in order: 1. **Sign in or create a lomi. account.** The link survives the auth detour and returns to the same enrollment. 2. **Organization.** Pick an existing lomi. organization or create a new one for this membership. 3. **Verification** (new merchants only). Business type, documents, and essentials, the same steps as the standard lomi. onboarding. Existing merchants skip this step. 4. **Business details.** Legal name (required), country, tax / registry identifiers, contact, and an optional external id you can use to reconcile with your own records. 5. **Payout method.** Bank or mobile money destination for the member's balance. Can be skipped and added later from **Balance**. 6. **Review.** Requested capabilities and the data-sharing terms version. After completion the member lands in their own lomi. dashboard in **member mode** (see [Member dashboard and login links](#member-dashboard-and-login-links)). ### Embedded onboarding If you would rather keep the member inside your product, mount the `onboarding` [embedded component](#embedded-components) with an account session. The member goes through the same steps (organization, verification, business details, payout method, review) without leaving your page. Use the hosted invite when you want the least integration work; use embedded onboarding when your product already has a settings area for sellers. ### Member statuses Statuses are per membership and per environment. They show in **Members** and on the member's Home. | Status | Meaning | Action required | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | In review | Enrollment is complete and verification is pending. Delegated payments are refused. | None from you. lomi. reviews new merchants; existing verified merchants clear quickly. | | Enabled | Verification passed and the granted capabilities are active. Payments, refunds, and transfers flow. | None. | | Restricted soon | A requirement is due by a deadline: a missing document, an expired identifier, or a payout method. | The member completes it from their dashboard before the date. Send a [login link](#member-dashboard-and-login-links) or show the `notification-banner` component. | | Restricted | The deadline passed. Delegated payments and transfers to this member are refused until the requirement is met. Payouts may be held. | The member completes the requirement; the status returns to Enabled once lomi. re-checks. | | Rejected | Verification failed. No payments. | Contact support with the member; a corrected enrollment may be needed. | | Suspended | You or lomi. suspended the membership (risk, disputes, or terms). Payments and transfers stop and payouts are held. | Resolve with lomi. support. You can lift a suspension you created from **Members**. | The API mirrors these states with `network_membership_not_active`, `network_account_not_active`, and `network_capability_missing` (see [Errors](#errors)). ### Capabilities Capabilities are granted per membership and environment. On activation, lomi. grants what the invite requested (`requested_capabilities`) or your Operator defaults. Adjust them later from **Members**. | Capability | Lets the Operator | | -------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `payment.create` | Create direct charges and checkout sessions for the member (`Lomi-Account` on `POST /checkout-sessions`, `POST /charge/*`). | | `refund.create` | Refund the member's transactions (`POST /refunds` with `Lomi-Account`). | | `customer.read` | Read the member's customers. | | `customer.write` | Create and update the member's customers. | | `transaction.read` | Read the member's transactions and refunds. | | `account.read` | Read the member's account and business details. | | `balance.read` | Read the member's balance (`GET /accounts/balance` with `Lomi-Account`). | | `transfer.receive` | Be the `destination` of destination charges and `POST /transfers`. | | `account.login_link` | Create login links into the member dashboard. | | `webhook.receive` | Receive Network webhooks for this member on your Operator endpoints. | ## Charge types All examples use the sandbox base URL and a test Operator key. Amounts are integers in minor units (XOF has none, so `10000` is 10 000 F CFA). ### Direct charges The member is the merchant of record. Send `Lomi-Account` and, optionally, `application_fee_amount`: ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/checkout-sessions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Lomi-Account: acct_1a2b3c4d5e6f7g8h" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency_code": "XOF", "title": "Order #12345", "application_fee_amount": 500, "success_url": "https://example.com/success", "cancel_url": "https://example.com/cancel" }' ``` The checkout session belongs to the Member Account: the hosted page shows the member's branding and payment methods, and the transaction appears in the member's Transactions. When the payment completes, the member is credited the net amount and your fee (`500` here) moves from the member to your Operator balance as an `operator_fee` transfer. The same header works on `POST /charge/wave` and `POST /charge/mtn`. ### Destination charges You are the merchant of record. Charge on your own account and name the member in `transfer_data`: ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/checkout-sessions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency_code": "XOF", "title": "Order #12345", "application_fee_amount": 500, "transfer_data": { "destination": "acct_1a2b3c4d5e6f7g8h" }, "success_url": "https://example.com/success", "cancel_url": "https://example.com/cancel" }' ``` Do not send `Lomi-Account` on a destination charge. The hosted page shows your branding and payment methods. At completion the payment lands on your balance and lomi. immediately creates a `destination` transfer of the payment minus `application_fee_amount` (and minus the lomi. processing fee when the member is the `fees_collector`). Pass `transfer_data.amount` to transfer a fixed amount instead. ### Separate charges and transfers Charge on your own account with a `transfer_group`, then pay one or several members later: ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/checkout-sessions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 15000, "currency_code": "XOF", "title": "Cart #95", "transfer_group": "ORDER_95", "success_url": "https://example.com/success", "cancel_url": "https://example.com/cancel" }' ``` Nothing moves to members at completion. When you are ready (after delivery, at the end of the day, or in a batch), create transfers with the same `transfer_group`; see [Transfers](#transfers). Your fee is simply what you do not transfer. ### Endpoints that accept `Lomi-Account` | Endpoint | Capability | | --------------------------------------------------------------------------- | ------------------ | | `POST /checkout-sessions` | `payment.create` | | `POST /charge/wave`, `POST /charge/mtn`, `POST /charge/card` | `payment.create` | | `GET /transactions`, `GET /transactions/{id}`, `GET /charge/card/{id}` | `transaction.read` | | `GET /refunds`, `GET /refunds/{id}` | `transaction.read` | | `POST /refunds` | `refund.create` | | `GET /customers`, `GET /customers/{id}`, `GET /customers/{id}/transactions` | `customer.read` | | `POST /customers`, `PATCH /customers/{id}` | `customer.write` | | `GET /accounts/balance` | `balance.read` | If `Lomi-Account` is sent to another endpoint, the API rejects the request instead of silently switching organization scope. The API key environment controls the request environment: a test key requires test-mode grants, a live key requires live-mode grants. Customer calls create and update customers under the member organization. lomi. records Network metadata on customers created by an Operator, and customer lists include customers created by that Operator or attached to delegated transactions. For endpoints that support `Idempotency-Key`, send a normal key. lomi. scopes it by membership and includes `Lomi-Account` in the fingerprint, so two Operators targeting the same member do not collide. ## Fees and settlement ### Fee rules Fee rules live in the dashboard **Fees** tab. A rule is `fixed`, `percentage` (basis points), or `blended`, with optional minimum and maximum. Make one rule the **Operator default** and, when a member needs different terms, assign a **per-member rule** from **Members**. Memberships activated without an explicit rule inherit the default at activation time. Each completed delegated payment writes a fee entry you can read in **Fees → Fee entries**. ### `application_fee_amount` Pass `application_fee_amount` on a direct or destination charge to override the fee rule for that payment. The value is in the payment currency and cannot exceed the payment amount. Leave it out to apply the rule. On separate charges the fee is implicit: it is the part you keep when you transfer. ### `fees_collector` and `losses_collector` * **`fees_collector: member`** (default): the lomi. processing fee is deducted from what the member receives. On a destination charge the default transfer is amount minus your fee minus the processing fee. * **`fees_collector: operator`**: the processing fee is charged to your Operator balance. On a direct charge lomi. adds a `processing_fee_cover` transfer from you to the member so the member stays whole. * **`losses_collector`** (default `member`): when a refund is larger than the balance that should pay it, lomi. writes a `loss_cover` transfer from the losses collector to that balance so the customer is refunded in full. ### What moves when | Event | Direct | Destination | Separate | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | Payment completes | Member credited net. `operator_fee` transfer member → operator. `processing_fee_cover` operator → member when you are the fees collector. | Operator credited. `destination` transfer operator → member for amount minus fee (and minus processing fee when the member is the fees collector). | Operator credited. No transfer. | | You call `POST /transfers` | Not used | Optional top-up | `separate` transfer operator → member | | Refund | Member balance debited. `fee_reversal` transfer operator → member for the proportional fee (`refund_application_fee`). | Operator balance debited. `transfer_reversal` member → operator for the proportional share (`reverse_transfer`). Fee kept or reversed per `refund_application_fee`. | Same as destination for every transfer in the `transfer_group`. | | Balance short on refund | `loss_cover` from the losses collector | `loss_cover` from the losses collector | `loss_cover` from the losses collector | Balances move in XOF. Payments in USD or EUR are converted at the settled rate and the transfer carries both `amount` / `currency_code` and `settled_amount` / `settled_currency`. ## Transfers Transfers use the Operator key **without** `Lomi-Account`, require `Idempotency-Key`, and go through a two-step confirmation so a bug cannot move money by accident. ### Create a transfer First call, no `confirmation_token`. lomi. returns a preview: ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/transfers" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Idempotency-Key: order-95-seller-1" \ -H "Content-Type: application/json" \ -d '{ "amount": 9000, "currency_code": "XOF", "destination": "acct_1a2b3c4d5e6f7g8h", "transfer_group": "ORDER_95", "description": "Payout for order 95" }' ``` ```json { "requires_confirmation": true, "confirmation_token": "...", "expires_at": "2026-09-09T12:10:00.000Z", "preview": { "amount": 9000, "currency_code": "XOF", "destination": "acct_1a2b3c4d5e6f7g8h", "transfer_group": "ORDER_95" } } ``` Second call, same body plus the token. This is the call that moves money and consumes the `Idempotency-Key`: ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/transfers" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Idempotency-Key: order-95-seller-1" \ -H "Content-Type: application/json" \ -d '{ "amount": 9000, "currency_code": "XOF", "destination": "acct_1a2b3c4d5e6f7g8h", "transfer_group": "ORDER_95", "description": "Payout for order 95", "confirmation_token": "..." }' ``` The token is valid for 10 minutes and bound to `amount`, `currency_code`, `destination`, and `transfer_group`. Changing any of them invalidates it; call again without a token for a new preview. Replaying the executing call with the same `Idempotency-Key` returns the original transfer with `Idempotency-Cache-Hit: true`. Optional fields: `source_transaction_id` (the payment this transfer settles), `description`, `metadata`. The destination must be an **Enabled** membership with `transfer.receive` for the key environment, and the amount cannot exceed your available balance. ### List and retrieve ```bash curl -sS "https://sandbox.api.lomi.africa/transfers?transfer_group=ORDER_95" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` `GET /transfers` returns a paginated list (`cursor`, `limit`) newest first, filterable by `destination`, `transfer_group`, `source_transaction_id`, and `transfer_type` (comma-separated). `GET /transfers/{id}` returns one transfer. Reference pages: [Create transfer](/api/transfers/TransfersController_create), [List transfers](/api/transfers/TransfersController_findAll), [Retrieve transfer](/api/transfers/TransfersController_findOne), [Reverse transfer](/api/transfers/TransfersController_reverse). ### Reverse a transfer ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/transfers/tr_.../reversals" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Idempotency-Key: order-95-seller-1-reversal" \ -H "Content-Type: application/json" \ -d '{ "amount": 4500, "description": "Order 95 partially cancelled" }' ``` Same two-step flow: the first call previews, the second call with `confirmation_token` executes. Omit `amount` to reverse the remaining unreversed amount. The member must have enough available balance to cover the reversal. Refunds on destination and separate charges reverse transfers for you (see [Refunds and liability](#refunds-and-liability)); use this endpoint for manual corrections. ### Transfer object | Field | Description | | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `id` | `tr_...` | | `object` | `transfer` | | `amount`, `currency_code` | Requested amount in the request currency | | `settled_amount`, `settled_currency` | Amount actually moved between balances (XOF) | | `transfer_type` | `destination`, `separate`, `operator_fee`, `processing_fee_cover`, `fee_reversal`, `transfer_reversal`, `loss_cover` | | `status` | `pending`, `posted`, `reversed`, `failed` | | `environment` | `test` or `live` | | `destination`, `source` | `acct_...` of the receiving and paying side | | `source_transaction_id` | Payment this transfer settles, when known | | `refund_id` | Refund that caused a reversal or fee reversal | | `reversed_transfer_id`, `reversed_amount` | Link and running total for reversals | | `transfer_group` | Your grouping key | | `description`, `metadata` | Free text and your own keys | | `created_at` | ISO 8601 | ## Refunds and liability Refund with `POST /refunds` as usual. Two Network flags control the money: * `reverse_transfer` (default `true`): on destination and separate charges, pull back the proportional share of the member transfer(s). Set `false` to refund the customer from your balance and leave the member whole. * `refund_application_fee` (default `true`): reverse your fee proportionally. Set `false` to keep your fee on a refunded payment. ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/refunds" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Idempotency-Key: refund-order-95" \ -H "Content-Type: application/json" \ -d '{ "transaction_id": "TXN_...", "amount": 5000, "reason": "requested_by_customer", "reverse_transfer": true, "refund_application_fee": true }' ``` For a **direct** charge, add `Lomi-Account` (capability `refund.create`). The refund is paid from the member balance and the proportional fee moves back from you to the member as a `fee_reversal` transfer. For **destination** and **separate** charges the refund is paid from your balance and the member share is pulled back with `transfer_reversal` transfers. Refunds follow the same two-step confirmation as transfers. Liability follows the merchant of record. On direct charges the member owns disputes and refund shortfalls; on destination and separate charges you do. `losses_collector` lets you change who covers a shortfall when the paying balance is empty: lomi. writes a `loss_cover` transfer so the customer is still refunded in full. Refunds emit `NETWORK_OPERATOR_FEE_REVERSED` and, when a transfer is pulled back, `NETWORK_TRANSFER_REVERSED`. ## Member dashboard and login links A connected member keeps a full lomi. account. When an organization is only a Network member (not an Operator itself), its dashboard switches to **member mode**, a reduced console: * **Home**: balance available to withdraw, recent activity, and a "Connected to" block naming your platform, with the `acct_...` id and membership status. * **Balance**: payout methods and payouts. Delegated payments and transfers credit this balance directly. * **Transactions**: every payment, including those you created on the member's behalf. * **Settings**: business details, team, payout methods. Payment links, catalog, invoicing, and the Operator console are hidden for member-only organizations. To drop a member into that dashboard from your product, create a login link (capability `account.login_link`): ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/network/accounts/acct_1a2b3c4d5e6f7g8h/login_links" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ```json { "url": "https://dashboard.lomi.africa/...", "expires_at": "2026-09-09T12:05:00.000Z" } ``` The URL is valid for 5 minutes and single use. Create it server-side when the member clicks, then redirect. Hand it to the member only; never email it or store it. ## Embedded components Embedded components render member surfaces inside your own pages: `payments`, `payouts`, `balance`, `onboarding`, and `notification-banner` (open requirements and Restricted soon warnings). 1. Server-side, create an account session for the member: ```bash curl -sS -X POST "https://sandbox.api.lomi.africa/network/account-sessions" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "account": "acct_1a2b3c4d5e6f7g8h", "components": { "payments": { "enabled": true }, "payouts": { "enabled": true }, "notification_banner": { "enabled": true }, "onboarding": { "enabled": false }, "balance": { "enabled": false } } }' ``` ```json { "client_secret": "nas_...", "embed_base_url": "https://dashboard.lomi.africa/embed" } ``` 2. In the browser, load the script and mount: ```html
``` Or declaratively, one component per element: ```html
``` `components` limits what the `client_secret` can render (keys `payments`, `payouts`, `balance`, `onboarding`, `notification_banner`, each with `enabled`); omit it, or omit a key, to allow that component. The secret is short-lived and scoped to one member. Create a new session on each page load and never expose your Operator key to the browser. ## Webhooks Register webhook endpoints on the **Operator** organization and subscribe to the Network events. Payloads carry the member (`network_account_id`, `public_account_id`, member organization), the environment, and the object concerned. | Event | When | Payload highlights | | ------------------------------- | -------------------------------------------------------------------------- | --------------------------------------- | | `NETWORK_PAYMENT_CREATED` | A delegated or destination payment completed | `transaction_id`, amount, charge type | | `NETWORK_OPERATOR_FEE_CREATED` | Your fee was settled to your balance | fee entry, `operator_fee` transfer id | | `NETWORK_OPERATOR_FEE_REVERSED` | A refund reversed part of your fee | `refund_id`, `fee_reversal` transfer id | | `NETWORK_TRANSFER_CREATED` | A `destination` or `separate` transfer posted | the transfer object | | `NETWORK_TRANSFER_REVERSED` | A transfer was pulled back by a refund or `POST /transfers/{id}/reversals` | the reversal, `reversed_transfer` | | `NETWORK_MEMBER_PAYOUT_PAID` | A member payout left lomi. | payout id, amount, member | Members with `webhook.receive` also get their own webhooks on their organization. Verify signatures and confirm with `GET /transactions/{id}` or `GET /transfers/{id}` before fulfilling; see [Handling webhooks](/build/reliability/handling-webhooks). ## Go live 1. **Test.** Open **Network** in the dashboard and finish the setup wizard (platform profile, default fee rule, `fees_collector`, `losses_collector`). Your Network is active for **test** right away: invite test members, run every charge type, transfer, and refund with a `lomi_sk_test_...` key against `https://sandbox.api.lomi.africa`. 2. **Request live access.** In **Network → Settings**, click **Request live access** and describe your platform. lomi. reviews the use case and the fee setup. 3. **Approval.** Once approved, live memberships and live capability grants become available. Invite members in live (or re-invite test members), register live webhooks, and switch to a `lomi_sk_live_...` key against `https://api.lomi.africa`. Members must also be verified in live: a member who was only enrolled in test goes through verification once when they accept the live invite. See [Go live](/start/go-live) for the merchant-side checklist. ## Testing * **Keys and hosts.** Use a `lomi_sk_test_...` Operator key against `https://sandbox.api.lomi.africa`. The key selects the environment; test grants and test memberships are separate from live. * **Member ids.** `acct_...` ids are per member and per environment. A member enrolled in test has a test id; the live id is issued when they join in live. * **Balances.** Test payments credit test balances only. Transfers, fee moves, and reversals in test never touch live money, so you can rehearse refunds and shortfalls (`loss_cover`) safely. * **Payments.** Pay test checkout sessions with the sandbox methods described in [Sandbox payments](/start/sandbox-payments). Test MTN and Wave charges complete in the ledger without calling the provider. * **Confirmation flow.** The two-step `confirmation_token` and `Idempotency-Key` requirements are identical in test and live; test your retry logic there. * **Errors.** Try a member without `transfer.receive`, a transfer above your balance, or `Lomi-Account` on an unsupported endpoint to see the errors below. ## Errors | Message | Meaning | | ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `network_account_not_found` | The `acct_...` id does not exist. | | `network_account_not_active` | The Member Account is Restricted, Rejected, or Suspended. | | `operator_not_active` | The API-key organization is not an active Operator in this environment. | | `Network requests require a secret API key` | Publishable keys cannot be used for delegated requests or transfers. | | `network_membership_not_found` | The Operator is not connected to that Member Account. | | `network_membership_not_active` | The membership exists but is In review, Restricted, or Suspended. | | `network_capability_missing` | The membership does not have the required capability for this environment. | | `confirmation_token is invalid or expired` | The transfer or refund token was reused, changed, or older than 10 minutes. Call again without a token. | # Organizations Source: https://docs.lomi.africa/build/platform/organizations Retrieve organization details and metrics. *** title: Organizations description: Retrieve organization details and metrics. ------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; The Organizations API provides access to your organization's details and business metrics. Most integrations should treat the **organization** as the primary scope for API keys, balances, and settings. The [Merchants](/build/platform/merchants) API remains useful for merchant profile reads and legacy paths that still expose a merchant id. ## Pricing configuration Each organization has a pricing mode that determines how transaction fees are resolved: * `volume_tiered`: dynamic pricing based on processed volume tiers * `fixed`: stable organization fee schedule * `custom`: negotiated fee schedule (also resolved from organization-level fee configuration) In practice: * `volume_tiered` organizations use their current tier fee structure for supported categories. * `fixed` and `custom` organizations use organization fee configuration directly. * Additional fees (for example refunds, disputes, international card surcharges, subscription add-ons) can still apply depending on your setup. For the operational fee values currently applied to your account, rely on dashboard pricing screens and your latest commercial agreement. ## Get organization details Retrieve details of your authenticated organization. ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'live', }); const org = await lomi.organizations.list(); console.log(`Organization: ${org[0].name}`); ``` ```python from lomi import LomiClient import os client = LomiClient( api_key=os.environ["LOMI_SECRET_KEY"], environment="test" ) orgs = client.organizations.list() print(f"Organization: {orgs[0]['name']}") ``` ```bash curl -X GET "https://api.lomi.africa/organizations" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Get organization by ID Retrieve a specific organization (must match your authenticated organization). ```typescript const org = await lomi.organizations.get('org_abc123...'); ``` ```python org = client.organizations.get('org_abc123...') ``` ```bash curl -X GET "https://api.lomi.africa/organizations/org_abc123..." \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` *** ## Get organization metrics Retrieve pre-calculated business metrics including MRR, ARR, revenue, and customer counts. ```typescript const metrics = await lomi.organizations.getMetrics(); console.log(`MRR: ${metrics.mrr} ${metrics.currency_code}`); console.log(`ARR: ${metrics.arr} ${metrics.currency_code}`); console.log(`Total Revenue: ${metrics.total_revenue}`); console.log(`Total Customers: ${metrics.total_customers}`); console.log(`Total Transactions: ${metrics.total_transactions}`); ``` ```python metrics = client.organizations.get_metrics() print(f"MRR: {metrics['mrr']} {metrics['currency_code']}") print(f"ARR: {metrics['arr']} {metrics['currency_code']}") print(f"Total Revenue: {metrics['total_revenue']}") print(f"Total Customers: {metrics['total_customers']}") ``` ```bash curl -X GET "https://api.lomi.africa/organizations/metrics" \ -H "X-API-KEY: $LOMI_SECRET_KEY" ``` ### Response ```json { "mrr": 50000, "arr": 600000, "total_revenue": 250000, "total_transactions": 1234, "total_customers": 567, "currency_code": "XOF", "calculated_at": "2024-01-15T00:00:00Z" } ``` *** ## Metrics Object | Field | Type | Description | | -------------------- | -------- | -------------------------------------------------------------------------- | | `mrr` | `number` | Monthly Recurring Revenue from active subscriptions (org default currency) | | `arr` | `number` | Annual Recurring Revenue (`mrr` × 12) | | `total_revenue` | `number` | Total completed payment volume, converted to org default currency | | `total_transactions` | `number` | Total transaction count | | `total_customers` | `number` | Total customer count | | `currency_code` | `string` | Currency for monetary values | | `calculated_at` | `string` | Calculation timestamp | *** ## Organization object These fields align with the **organization** resource returned by `GET /organizations` (see [API reference](/api)). Pricing **modes** (`volume_tiered`, `fixed`, `custom`) are explained above and in [Pricing](/start/merchant-of-record/pricing); they are not required to appear as a dedicated column on every organization payload. | Field | Type | Description | | ------------------------- | ------------------ | ---------------------------------------------------------------------- | | `organization_id` | `string` | Unique organization identifier | | `name` | `string` | Organization name | | `email` | `string` | Contact email | | `phone_number` | `string` | Primary phone | | `verification_status` | `string` | `unverified`, `starter`, or `verified` | | `website_url` | `string` \| `null` | Website | | `logo_url` | `string` \| `null` | Logo URL | | `status` | `string` | `active`, `inactive`, or `suspended` | | `default_currency` | `string` | Default currency (`XOF`, `USD`, `EUR`) | | `slug` | `string` \| `null` | URL-friendly slug | | `storefront_enabled` | `boolean` | Storefront enabled | | `total_revenue` | `number` \| `null` | Total revenue (when present) | | `total_transactions` | `number` \| `null` | Transaction count | | `total_merchants` | `number` \| `null` | Merchant count | | `total_customers` | `number` \| `null` | Customer count | | `mrr` | `number` | Monthly recurring revenue (active subscriptions, org default currency) | | `arr` | `number` | Annual recurring revenue (`mrr` × 12) | | `merchant_lifetime_value` | `number` | Predicted per-customer lifetime value (Customer Value x Avg Lifespan) | | `employee_number` | `string` \| `null` | Employee range label | | `industry` | `string` \| `null` | Industry | | `has_payout_pin` | `boolean` | Whether org requires PIN for manual payouts | | `is_starter_business` | `boolean` | Starter business flag | | `metadata` | `object` | Additional metadata | | `created_at` | `string` | Creation timestamp | | `updated_at` | `string` | Last update timestamp | | `is_deleted` | `boolean` | Soft-deleted | | `deleted_at` | `string` \| `null` | Deletion time | *** ## Error Responses | Status | Description | | ------ | --------------------------------------- | | `401` | Invalid or missing API key | | `404` | Organization not found or access denied | # Go SDK Source: https://docs.lomi.africa/build/sdks/go Official Go SDK for lomi. payments API. *** title: 'Go SDK' description: 'Official Go SDK for lomi. payments API.' ------------------------------------------------------ import { Callout } from '@/components/docs/docs-callout'; # Go SDK Official Go SDK for lomi.. payments API. Works with any Go 1.18+ application. ## Installation ```bash go get github.com/lomiafrica/lomi-go ``` ## Quick start ```go package main import ( "context" "fmt" "os" lomi "github.com/lomiafrica/lomi-go" ) func main() { configuration := lomi.NewConfiguration() client := lomi.NewAPIClient(configuration) // Configure authentication auth := context.WithValue( context.Background(), lomi.ContextAPIKeys, map[string]lomi.APIKey{ "ApiKeyAuth": {Key: os.Getenv("LOMI_SECRET_KEY")}, }, ) // Now use auth context for all API calls } ``` ## Examples ### List customers ```go customers, _, err := client.CustomersAPI.ListCustomers(auth).Execute() if err != nil { fmt.Printf("Error: %v\n", err) return } for _, customer := range customers.Data { fmt.Printf("%s: %s\n", *customer.Id, *customer.Name) } ``` ### Create a customer ```go customerData := lomi.CustomersCreate{ Name: lomi.PtrString("Moussa Keita"), Email: lomi.PtrString("moussa@example.com"), PhoneNumber: lomi.PtrString("+22370123456"), } customer, _, err := client.CustomersAPI.CreateCustomer(auth). CustomersCreate(customerData). Execute() if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Created customer: %s\n", *customer.Id) ``` ### Create a checkout session ```go sessionData := lomi.CheckoutSessionsCreate{ Amount: lomi.PtrInt32(5000), // 5,000 F CFA Currency: lomi.PtrString("XOF"), SuccessUrl: lomi.PtrString("https://yoursite.com/success"), CancelUrl: lomi.PtrString("https://yoursite.com/cancel"), } session, _, err := client.CheckoutSessionsAPI.CreateCheckoutSession(auth). CheckoutSessionsCreate(sessionData). Execute() if err != nil { fmt.Printf("Error: %v\n", err) return } // Redirect customer to session.Url fmt.Printf("Checkout URL: %s\n", *session.Url) ``` ### List transactions ```go transactions, _, err := client.TransactionsAPI.ListTransactions(auth).Execute() if err != nil { fmt.Printf("Error: %v\n", err) return } for _, tx := range transactions.Data { fmt.Printf("%s: %d %s - %s\n", *tx.Id, *tx.Amount, *tx.Currency, *tx.Status) } ``` ### Create a payment link ```go linkData := lomi.PaymentLinksCreate{ Amount: lomi.PtrInt32(10000), Currency: lomi.PtrString("XOF"), Description: lomi.PtrString("Premium subscription"), Reusable: lomi.PtrBool(true), } link, _, err := client.PaymentLinksAPI.CreatePaymentLink(auth). PaymentLinksCreate(linkData). Execute() if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Share this link: %s\n", *link.Url) ``` ## Error handling ```go customer, response, err := client.CustomersAPI.RetrieveCustomer(auth, "invalid_id").Execute() if err != nil { // Check for API error details if apiErr, ok := err.(*lomi.GenericOpenAPIError); ok { fmt.Printf("API Error: %s\n", apiErr.Error()) fmt.Printf("Response body: %s\n", string(apiErr.Body())) } // Check HTTP status if response != nil { fmt.Printf("HTTP Status: %d\n", response.StatusCode) } return } ``` ## Webhook handling ```go package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "io" "log" "net/http" "os" ) type WebhookEvent struct { Type string `json:"type"` Data map[string]interface{} `json:"data"` } func webhookHandler(w http.ResponseWriter, r *http.Request) { signature := r.Header.Get("x-lomi-signature") secret := os.Getenv("LOMI_WEBHOOK_SECRET") body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read body", http.StatusBadRequest) return } // Verify signature mac := hmac.New(sha256.New, []byte(secret)) mac.Write(body) expectedSignature := hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(signature), []byte(expectedSignature)) { http.Error(w, "Invalid signature", http.StatusBadRequest) return } var event WebhookEvent if err := json.Unmarshal(body, &event); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } switch event.Type { case "PAYMENT_SUCCEEDED": log.Printf("Payment succeeded: %v", event.Data) // Handle payment default: log.Printf("Unhandled event: %s", event.Type) } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]bool{"received": true}) } func main() { http.HandleFunc("/webhook", webhookHandler) log.Println("Webhook listener running on :3000") log.Fatal(http.ListenAndServe(":3000", nil)) } ``` ## Pointer helpers The SDK provides pointer helper functions for all basic types: ```go // Use these helpers for optional fields name := lomi.PtrString("Customer Name") amount := lomi.PtrInt32(5000) active := lomi.PtrBool(true) rate := lomi.PtrFloat64(0.025) ``` ## Available APIs | API | Methods | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `CustomersAPI` | `ListCustomers`, `CreateCustomer`, `RetrieveCustomer`, `UpdateCustomer`, `DeleteCustomer` | | `CheckoutSessionsAPI` | `ListCheckoutSessions`, `CreateCheckoutSession`, `RetrieveCheckoutSession`, `UpdateCheckoutSession`, `DeleteCheckoutSession` | | `TransactionsAPI` | `ListTransactions`, `RetrieveTransaction` | | `PaymentLinksAPI` | `ListPaymentLinks`, `CreatePaymentLink`, `RetrievePaymentLink`, `UpdatePaymentLink`, `DeletePaymentLink` | | `ProductsAPI` | `ListProducts`, `CreateProduct`, `RetrieveProduct`, `UpdateProduct`, `DeleteProduct` | | `SubscriptionsAPI` | `ListSubscriptions`, `CreateSubscription`, `RetrieveSubscription`, `UpdateSubscription`, `DeleteSubscription` | | `RefundsAPI` | `ListRefunds`, `CreateRefund`, `RetrieveRefund` | | `PayoutsAPI` | `ListPayouts`, `CreatePayout`, `RetrievePayout` (self and beneficiary via `destination`) | | `ChargesAPI` | `CreateWaveCharge`, `CreateMtnCharge`, `CreateCardCharge`, `GetCardCharge`, `CancelCardCharge` | | `PaymentRequestsAPI` | `ListPaymentRequests`, `CreatePaymentRequest`, `RetrievePaymentRequest` | | `DiscountCouponsAPI` | `ListDiscountCoupons`, `CreateDiscountCoupon`, `RetrieveDiscountCoupon`, `UpdateDiscountCoupon`, `DeleteDiscountCoupon` | | `WebhookDeliveryLogsAPI` | `ListWebhookDeliveryLogs`, `RetrieveWebhookDeliveryLog` | | `WebhooksAPI` | `ListWebhooks`, `CreateWebhook`, `RetrieveWebhook`, `UpdateWebhook`, `DeleteWebhook` | ## Need help? * [API reference](/api) - Full API documentation * [GitHub Issues](https://github.com/lomiafrica/lomi./issues) - Report bugs * [Discord](https://discord.gg/yb4FnBmh) - Community support # SDKs Overview Source: https://docs.lomi.africa/build/sdks Official lomi. SDKs to integrate payments in your preferred language. *** title: 'SDKs Overview' description: 'Official lomi. SDKs to integrate payments in your preferred language.' ------------------------------------------------------------------------------------ import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from '@/components/docs/docs-callout'; # lomi. SDKs Official SDKs for lomi.. payments API. Choose the SDK that matches your tech stack and start accepting payments in minutes. First TypeScript call: [SDK quickstart](/start/sdk-quickstart). This page is the language matrix and service map. ## Quick installation | Language | Package | Installation | | ---------------- | --------------------- | -------------------------------------- | | **TypeScript** | `@lomi./sdk` | `npm install @lomi./sdk` | | **Embed** | `@lomi./embed` | `npm install @lomi./embed` | | **Python** | `lomi-sdk` | `pip install lomi-sdk` | | **Go** | `lomi-go` | `go get github.com/lomiafrica/lomi-go` | | **PHP** | `lomi/lomi-sdk` | Via Composer (see below) | | **React Native** | `@lomi./react-native` | `npm install @lomi./react-native` | The curated TypeScript client is generated from the **public merchant OpenAPI** contract plus a strict `_expected-public-operations.json` allowlist. Other SDKs mirror the same endpoints but may diverge stylistically, always defer to **`openapi.json`** for truth. *** ## Available services All SDKs provide access to these API services: | Service | Description | | ------------------ | -------------------------------------------------- | | `customers` | Create and manage customers | | `checkoutSessions` | Create hosted checkout pages | | `transactions` | View transaction history | | `paymentLinks` | Generate shareable payment links | | `paymentRequests` | Request payments from customers | | `products` | Manage your product catalog | | `prices` | Configure product pricing | | `subscriptions` | Recurring billing management | | `refunds` | Process refunds | | `coupons` | Create and manage coupons | | `payouts` | Withdraw (self) or pay beneficiaries | | `charges` | Wave, MTN, and embedded card charges | | `webhooks` | Configure webhook endpoints and inspect deliveries | | `organizations` | Organization details and metrics | | `merchants` | Merchant profile and revenue reads | | `providers` | Enabled payment providers | *** ## Choose your SDK *** ## Requirements | SDK | Minimum version | | ---------- | --------------- | | TypeScript | Node.js 18+ | | Python | Python 3.9+ | | Go | Go 1.18+ | | PHP | PHP 8.0+ | *** ## Need help? * **Email**: [hello@lomi.africa](mailto:hello@lomi.africa) * **Discord**: [Join our community](https://discord.gg/yb4FnBmh) * **GitHub**: [lomiafrica/lomi.](https://github.com/lomiafrica/lomi.) # PHP SDK Source: https://docs.lomi.africa/build/sdks/php Official PHP SDK for lomi. payments API. *** title: 'PHP SDK' description: 'Official PHP SDK for lomi. payments API.' ------------------------------------------------------- import { Callout } from '@/components/docs/docs-callout'; # PHP SDK Official PHP SDK for lomi.. payments API. Works with Laravel, Symfony, WordPress, and any PHP 8.0+ application. Due to our monorepo structure, the PHP SDK is **not available on Packagist**. Install directly from GitHub using Composer. ## Installation Add to your `composer.json`: ```json { "repositories": [ { "type": "vcs", "url": "https://github.com/lomiafrica/lomi./" } ], "require": { "lomi/lomi-sdk": "dev-main#apps/sdks/php" } } ``` Then run: ```bash composer install ``` ## Quick start ```php setApiKey('X-API-KEY', getenv('LOMI_SECRET_KEY')); // For sandbox/testing // $config->setHost('https://sandbox.api.lomi.africa'); $apiClient = new ApiClient($config); ``` ## Examples ### List customers ```php listCustomers(); foreach ($customers->getData() as $customer) { echo $customer->getId() . ': ' . $customer->getName() . "\n"; } ``` ### Create a customer ```php 'Aminata Touré', 'email' => 'aminata@example.com', 'phone_number' => '+2210712345678' ]); $customer = $customersApi->createCustomer($customerData); echo 'Created customer: ' . $customer->getId(); ``` ### Create a checkout session ```php 5000, // 5,000 F CFA 'currency' => 'XOF', 'success_url' => 'https://yoursite.com/success', 'cancel_url' => 'https://yoursite.com/cancel', 'metadata' => ['order_id' => 'order_123'] ]); $session = $checkoutApi->createCheckoutSession($sessionData); // Redirect customer to checkout header('Location: ' . $session->getUrl()); exit; ``` ### List transactions ```php listTransactions(); foreach ($transactions->getData() as $tx) { echo sprintf( "%s: %d %s - %s\n", $tx->getId(), $tx->getAmount(), $tx->getCurrency(), $tx->getStatus() ); } ``` ### Create a payment link ```php 10000, 'currency' => 'XOF', 'description' => 'Premium subscription', 'reusable' => true ]); $link = $paymentLinksApi->createPaymentLink($linkData); echo 'Share this link: ' . $link->getUrl(); ``` ## Error handling ```php retrieveCustomer('invalid_id'); } catch (ApiException $e) { echo 'API Error: ' . $e->getCode() . "\n"; echo 'Message: ' . $e->getMessage() . "\n"; echo 'Response: ' . $e->getResponseBody() . "\n"; } ``` ## Laravel integration ### Service Provider ```php app->singleton(ApiClient::class, function () { $config = Configuration::getDefaultConfiguration() ->setApiKey('X-API-KEY', config('services.lomi.api_key')); return new ApiClient($config); }); } } ``` ### Config ```php [ 'api_key' => env('LOMI_SECRET_KEY'), 'webhook_secret' => env('LOMI_WEBHOOK_SECRET'), ], ]; ``` ### Webhook Controller ```php header('x-lomi-signature'); $secret = config('services.lomi.webhook_secret'); $payload = $request->getContent(); $expectedSignature = hash_hmac('sha256', $payload, $secret); if (!hash_equals($expectedSignature, $signature)) { return response()->json(['error' => 'Invalid signature'], 400); } $event = $request->all(); switch ($event['type']) { case 'PAYMENT_SUCCEEDED': // Handle payment break; } return response()->json(['received' => true]); } } ``` ### Routes ```php withoutMiddleware(['web', 'csrf']); ``` ## WordPress integration ```php 'POST', 'callback' => 'handle_lomi_webhook', 'permission_callback' => '__return_true', ]); }); function handle_lomi_webhook(WP_REST_Request $request) { $signature = $request->get_header('x-lomi-signature'); $secret = get_option('lomi_webhook_secret'); $payload = $request->get_body(); $expected = hash_hmac('sha256', $payload, $secret); if (!hash_equals($expected, $signature)) { return new WP_Error('invalid_signature', 'Invalid signature', ['status' => 400]); } $event = $request->get_json_params(); if ($event['type'] === 'PAYMENT_SUCCEEDED') { // Handle payment } return ['received' => true]; } ``` ## Available APIs | API Class | Methods | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `CustomersApi` | `listCustomers`, `createCustomer`, `retrieveCustomer`, `updateCustomer`, `deleteCustomer` | | `CheckoutSessionsApi` | `listCheckoutSessions`, `createCheckoutSession`, `retrieveCheckoutSession`, `updateCheckoutSession`, `deleteCheckoutSession` | | `TransactionsApi` | `listTransactions`, `retrieveTransaction` | | `PaymentLinksApi` | `listPaymentLinks`, `createPaymentLink`, `retrievePaymentLink`, `updatePaymentLink`, `deletePaymentLink` | | `ProductsApi` | `listProducts`, `createProduct`, `retrieveProduct`, `updateProduct`, `deleteProduct` | | `SubscriptionsApi` | `listSubscriptions`, `createSubscription`, `retrieveSubscription`, `updateSubscription`, `deleteSubscription` | | `RefundsApi` | `listRefunds`, `createRefund`, `retrieveRefund` | | `PayoutsApi` | `listPayouts`, `createPayout`, `retrievePayout` | | `ChargesApi` | `createWaveCharge`, `createMtnCharge`, `createCardCharge`, `getCardCharge`, `cancelCardCharge` | | `PaymentRequestsApi` | `listPaymentRequests`, `createPaymentRequest`, `retrievePaymentRequest` | | `DiscountCouponsApi` | `listDiscountCoupons`, `createDiscountCoupon`, `retrieveDiscountCoupon`, `updateDiscountCoupon`, `deleteDiscountCoupon` | | `WebhookDeliveryLogsApi` | `listWebhookDeliveryLogs`, `retrieveWebhookDeliveryLog` | | `WebhooksApi` | `listWebhooks`, `createWebhook`, `retrieveWebhook`, `updateWebhook`, `deleteWebhook` | ## Need help? * [API reference](/api) - Full API documentation * [GitHub Issues](https://github.com/lomiafrica/lomi./issues) - Report bugs * [Discord](https://discord.gg/yb4FnBmh) - Community support # Python SDK Source: https://docs.lomi.africa/build/sdks/python Official Python SDK for lomi. payments API. *** title: Python SDK description: Official Python SDK for lomi. payments API. -------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; Official Python SDK for lomi.. payments API. Works with Django, Flask, FastAPI, and any Python 3.9+ application. ## Installation ```bash pip install lomi-sdk ``` Or with Poetry: ```bash poetry add lomi-sdk ``` ## Quick start ```python import os from lomi import LomiClient client = LomiClient( api_key=os.environ["LOMI_SECRET_KEY"], environment="test" # 'test' for sandbox, 'live' for production ) ``` **Environments:** * `'test'` → `https://sandbox.api.lomi.africa` * `'live'` → `https://api.lomi.africa` *** ## Payment Examples ### Create a Checkout Session ```python session = client.checkout_sessions.create({ "amount": 10000, "currency_code": "XOF", "title": "Premium Subscription", "description": "Monthly access to premium features", "customer_email": "customer@example.com", "success_url": "https://yoursite.com/success", "cancel_url": "https://yoursite.com/cancel", "metadata": {"order_id": "ORD-123"} }) print(f"Redirect to: {session['checkout_url']}") ``` ### Create a Payment Link ```python link = client.payment_links.create({ "link_type": "product", "title": "Pro Plan", "currency_code": "XOF", "product_id": "prod_abc123...", "allow_coupon_code": True }) print(f"Share this link: {link['url']}") ``` ### List Transactions with Filters ```python transactions = client.transactions.list( status="completed", provider="WAVE", startDate="2024-01-01T00:00:00Z", pageSize=50 ) for tx in transactions: print(f"{tx['id']}: {tx['gross_amount']} {tx['currency_code']}") ``` *** ## Customer Management ### Create a Customer ```python customer = client.customers.create({ "name": "Fatou Diop", "email": "fatou@example.com", "phone_number": "+221771234567", "country": "Senegal", "city": "Dakar", "metadata": {"source": "website"} }) print(f"Customer ID: {customer['id']}") ``` ### Get Customer Transactions ```python transactions = client.customers.get_transactions("cus_abc123...") ``` *** ## Products & Subscriptions ### Create a Product ```python product = client.products.create({ "name": "Premium Plan", "description": "Full access to all features", "product_type": "recurring", "prices": [ { "amount": 15000, "currency_code": "XOF", "billing_interval": "month", "is_default": True } ], "trial_enabled": True, "trial_period_days": 7 }) ``` ### Add a New Price ```python price = client.products.add_price("prod_abc123...", { "amount": 150000, "currency_code": "XOF", "billing_interval": "year" }) ``` ### Cancel a Subscription ```python cancelled = client.subscriptions.cancel("sub_abc123...", { "cancel_at_period_end": True, "reason": "Customer request" }) ``` *** ## Payouts ### Initiate a Withdrawal ```python payout = client.payouts.create({ "amount": 100000, "currency_code": "XOF", "payout_method_id": "pm_abc123...", "description": "Monthly withdrawal" }) ``` ### Pay a Beneficiary ```python beneficiary_payout = client.beneficiary_payouts.create({ "amount": 50000, "currency_code": "XOF", "beneficiary_phone": "+221771234567", "beneficiary_name": "Contractor Name", "provider_code": "WAVE" }) ``` *** ## Account & Organization ### Get Account Balance ```python balances = client.accounts.get_balance() xof_balance = client.accounts.get_balance(currency="XOF") print(f"Available: {xof_balance['available']}") ``` ### Get Organization Metrics ```python metrics = client.organizations.get_metrics() print(f"MRR: {metrics['mrr']}") print(f"Total Customers: {metrics['total_customers']}") ``` *** ## Error Handling ```python from lomi import LomiClient, LomiError, LomiNotFoundError try: customer = client.customers.get("invalid_id") except LomiNotFoundError: print("Customer not found") except LomiError as e: print(f"API Error [{e.status_code}]: {e.message}") ``` *** ## Django Integration ```python # settings.py LOMI_SECRET_KEY = os.environ.get("LOMI_SECRET_KEY") LOMI_WEBHOOK_SECRET = os.environ.get("LOMI_WEBHOOK_SECRET") # views.py import hmac import hashlib import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.conf import settings @csrf_exempt def lomi_webhook(request): signature = request.headers.get("x-lomi-signature") expected = hmac.new( settings.LOMI_WEBHOOK_SECRET.encode(), request.body, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(signature, expected): return JsonResponse({"error": "Invalid signature"}, status=400) event = json.loads(request.body) if event["type"] == "PAYMENT_SUCCEEDED": # Handle successful payment transaction = event["data"] print(f"Payment received: {transaction['gross_amount']}") return JsonResponse({"received": True}) ``` *** ## Flask Integration ```python import hmac import hashlib import os from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/webhook", methods=["POST"]) def webhook(): signature = request.headers.get("x-lomi-signature") secret = os.environ["LOMI_WEBHOOK_SECRET"].encode() expected = hmac.new(secret, request.data, hashlib.sha256).hexdigest() if not hmac.compare_digest(signature, expected): return jsonify({"error": "Invalid signature"}), 400 event = request.json if event["type"] == "PAYMENT_SUCCEEDED": # Handle payment pass return jsonify({"received": True}) ``` *** ## Available Services | Service | Methods | | ----------------------- | ---------------------------------------------------------------------- | | `accounts` | `list`, `get`, `get_balance`, `get_balance_breakdown`, `check_balance` | | `beneficiary_payouts` | `list`, `get`, `create` | | `checkout_sessions` | `list`, `get`, `create` | | `customers` | `list`, `get`, `create`, `update`, `delete`, `get_transactions` | | `discount_coupons` | `list`, `get`, `create`, `get_performance` | | `organizations` | `list`, `get`, `get_metrics` | | `payment_links` | `list`, `get`, `create` | | `payment_requests` | `list`, `get`, `create` | | `payouts` | `list`, `get`, `create` | | `products` | `list`, `get`, `create`, `add_price`, `set_default_price` | | `refunds` | `list`, `get`, `create` | | `subscriptions` | `list`, `get`, `get_by_customer`, `cancel` | | `transactions` | `list`, `get` | | `webhook_delivery_logs` | `list`, `get` | | `webhooks` | `list`, `get`, `create`, `update`, `delete` | *** ## Resources * [Getting Started](/start/create-account) * [API reference](/api) * [GitHub](https://github.com/lomiafrica/lomi.) * [Discord Community](https://discord.gg/yb4FnBmh) # TypeScript SDK Source: https://docs.lomi.africa/build/sdks/typescript Official TypeScript/JavaScript SDK for lomi. payments API. *** title: TypeScript SDK description: Official TypeScript/JavaScript SDK for lomi. payments API. ----------------------------------------------------------------------- import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from '@/components/docs/docs-callout'; Official Node.js/TypeScript SDK for lomi.. payments API. Works with Node.js, Deno, Bun, and browser environments. ## Installation `bash npm install @lomi./sdk ` `bash pnpm add @lomi./sdk ` `bash yarn add @lomi./sdk ` `bash bun add @lomi./sdk ` ## Before you start 1. [Create a lomi. account](https://dashboard.lomi.africa) and complete onboarding. 2. Copy a **test secret key** from **Settings → Access tokens** (`lomi_sk_test_…`). See [Access tokens](/start/api-keys). 3. Add it to a `.env` file (see [Environment variables](/start/api-keys)). 4. Install the SDK (below), set `environment: 'test'`, then run the [first API call](#first-api-call-sandbox) example. ## Environments and base URLs Use **`test`** while building. The SDK maps `environment` to the API host automatically: | `environment` | API base URL | Secret key prefix | | ------------- | --------------------------------- | ----------------- | | `'test'` | `https://sandbox.api.lomi.africa` | `lomi_sk_test_…` | | `'live'` | `https://api.lomi.africa` | `lomi_sk_live_…` | You can override the host with `baseUrl` if needed, but matching **key prefix** and **`environment`** is enough for most integrations. ## Quick start ```typescript import { LomiSDK } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'test', // sandbox: use 'live' only in production }); ``` ## First API call (sandbox) Copy-paste runnable example: install the SDK, set `LOMI_SECRET_KEY` in `.env`, then fetch your sandbox balance. ```typescript import { LomiSDK, LomiAuthError, LomiNotFoundError } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'test', }); async function main() { try { const balance = await lomi.accounts.getBalance(); console.log('Balance:', balance); } catch (error) { if (error instanceof LomiAuthError) { console.error(`Auth failed [${error.statusCode}]: ${error.message}`); if (error.requestId) console.error('request_id:', error.requestId); process.exit(1); } throw error; } } main(); ``` ```bash filename=".env" LOMI_SECRET_KEY=lomi_sk_test_xxxxxxxxxxxxxxxxxxxxxx ``` ```bash filename="Terminal" npm install @lomi./sdk # load .env with your tooling (dotenv, Next.js, etc.) npx tsx first-call.ts ``` Expected: balance payload from `GET https://sandbox.api.lomi.africa/accounts/balance`. For raw HTTP without the SDK, see [API integration](/start/first-payment). *** ## Payment Examples ### Create a Checkout Session ```typescript const session = await lomi.checkoutSessions.create({ amount: 10000, currency_code: 'XOF', title: 'Premium Subscription', description: 'Monthly access to premium features', customer_email: 'customer@example.com', success_url: 'https://yoursite.com/success', cancel_url: 'https://yoursite.com/cancel', metadata: { order_id: 'ORD-123' }, }); console.log('Redirect to:', session.checkout_url); ``` ### Create a Payment Link ```typescript const link = await lomi.paymentLinks.create({ link_type: 'product', title: 'Pro Plan', currency_code: 'XOF', product_id: 'prod_abc123...', allow_coupon_code: true, }); console.log('Share this link:', link.url); ``` ### List Transactions with Filters ```typescript const transactions = await lomi.transactions.list({ status: 'completed', provider: 'WAVE', startDate: '2024-01-01T00:00:00Z', pageSize: 50, }); for (const tx of transactions) { console.log(`${tx.id}: ${tx.gross_amount} ${tx.currency_code}`); } ``` *** ## Customer Management ### Create a Customer ```typescript const customer = await lomi.customers.create({ name: 'Amadou Ba', email: 'amadou@example.com', phone_number: '+221771234567', country: 'Senegal', city: 'Dakar', metadata: { source: 'website' }, }); console.log('Customer ID:', customer.id); ``` ### Get Customer Transactions ```typescript const transactions = await lomi.customers.getTransactions('cus_abc123...'); ``` *** ## Products & Subscriptions ### Create a Product ```typescript const product = await lomi.products.create({ name: 'Premium Plan', description: 'Full access to all features', product_type: 'recurring', prices: [ { amount: 15000, currency_code: 'XOF', billing_interval: 'month', is_default: true, }, ], trial_enabled: true, trial_period_days: 7, }); ``` ### Add a New Price ```typescript const price = await lomi.products.addPrice('prod_abc123...', { amount: 150000, currency_code: 'XOF', billing_interval: 'year', }); ``` ### Cancel a Subscription ```typescript const cancelled = await lomi.subscriptions.cancel('sub_abc123...', { cancellation_reason: 'Customer request', }); ``` *** ## Payouts & rails-specific calls ### Payout (`POST /payouts`) ```typescript await lomi.payouts.create({ destination: 'beneficiary', rail: 'wave', amount: 10000, currency_code: 'XOF', recipient: { name: 'Aicha Diallo', phone: '+221771234567' }, }); ``` ### Direct mobile-money charge (`POST /charge/wave`) ```typescript await lomi.charges.createWaveCharge({ amount: 5000, currency: 'XOF', customer: { name: 'Moussa Ndiaye', phoneNumber: '+221771234567', email: 'moussa@example.com', }, }); ``` ### Embedded card charge (`POST /charge/card`) ```typescript const charge = await lomi.charges.createCardCharge({ amount: 2500, currency_code: 'XOF', customer_email: 'buyer@example.com', customer_name: 'Buyer Name', }); // Use charge.data.client_secret with Payment Elements on your frontend console.log(charge.data?.client_secret); ``` ### Switch charge, server-side card authorization (`POST /charge/switch`) For PCI-compliant fintech integrations that authorize cards server-side (not hosted checkout): ```typescript const result = await lomi.charges.createSwitchCharge( { amount: 10000, currency_code: 'XOF', pan: '4221941234569109', expiry: '06/30', cvv: '123', card_holder_name: 'Amadou Ba', }, { idempotencyKey: 'switch-attempt-001' }, ); if (result.status === 'redirect_3ds') { console.log('Complete 3DS at:', result.three_ds_url); } ``` Switch requires a PCI-DSS-compliant integration. For most merchants, use [Payment Elements](/build/sdks/typescript#payment-elements) or [checkout sessions](/api/checkout-sessions/CheckoutSessionsController_create) instead. *** ## Account & Organization ### Get Account Balance ```typescript const balances = await lomi.accounts.getBalance(); const xofBalance = await lomi.accounts.getBalance({ currency: 'XOF' }); console.log('Available:', xofBalance.available); ``` ### Get Organization Metrics ```typescript const metrics = await lomi.organizations.getMetrics(); console.log('MRR:', metrics.mrr); console.log('Total Customers:', metrics.total_customers); ``` *** ## Error Handling The SDK throws typed `LomiError` subclasses with the API message, HTTP status, error code, and `request_id`: ```typescript import { LomiSDK, LomiAuthError, LomiNotFoundError, LomiValidationError, LomiRateLimitError, } from '@lomi./sdk'; try { const customer = await lomi.customers.get('invalid_id'); } catch (error) { if (error instanceof LomiNotFoundError) { console.error('Customer not found:', error.message); } else if (error instanceof LomiValidationError) { console.error('Invalid request:', error.details); } else if (error instanceof LomiRateLimitError) { console.error('Rate limited, retry later'); } else if (error instanceof LomiAuthError) { console.error(`Auth error [${error.statusCode}]: ${error.message}`); } console.error('request_id:', error.requestId); } ``` *** ## Configuration Options Each `LomiSDK` instance is isolated, safe for multi-tenant workers and tests: ```typescript const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, environment: 'test', // 'live' in production account: 'acct_connected_…', // default Lomi-Account header (operator mode) timeout: 30_000, // request timeout in ms (default: 30000) retries: 2, // retry idempotent GETs + 429s with backoff headers: { 'X-Custom': '1' }, }); ``` ### Per-request options Pass on any create-style call: ```typescript await lomi.checkoutSessions.create(body, { idempotencyKey: 'checkout-001', // Idempotency-Key header account: 'acct_other_…', // override Lomi-Account for this call signal: abortController.signal, // AbortSignal }); ``` *** ## Webhooks Verify incoming webhook signatures without hand-rolling HMAC: ```typescript const valid = lomi.webhooks.verifySignature( rawBody, // string or Buffer (use raw body, not parsed JSON) req.headers['x-lomi-signature'], process.env.LOMI_WEBHOOK_SECRET!, ); if (!valid) return res.status(401).send('Invalid signature'); ``` *** ## Auto-pagination List endpoints expose an async iterator via `listAll`: ```typescript for await (const tx of lomi.transactions.listAll({ status: 'completed' })) { console.log(tx.id, tx.gross_amount); } ``` *** ## Payment Elements ```typescript import { loadLomi } from '@lomi./sdk'; // Your lomi_pk_… is validated; Elements run on lomi.'s PCI platform infrastructure. const elements = await loadLomi('lomi_pk_test_…'); ``` Payment Elements initialize on lomi.'s platform account. Your publishable key identifies your merchant context; you never handle raw card numbers. *** ## Available Services | Service | Methods | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `accounts` | `getBalance`, `getBalanceBreakdown`, `checkBalance` | | `charges` | `createWaveCharge`, `createMtnCharge`, `createCardCharge`, `createSwitchCharge`, `getCardCharge`, `cancelCardCharge` | | `checkoutSessions` | `list`, `listAll`, `get`, `create` | | `customers` | `list`, `listAll`, `get`, `create`, `update`, `delete`, `getTransactions`, `getSubscriptions`, `createPortalSession`, `getPortalAudit` | | `coupons` | `list`, `listAll`, `get`, `create`, `getPerformance` | | `disputes` | `list`, `listAll`, `get` | | `logs` | `list`, `listAll`, `get` | | `merchants` | `get`, `getArr`, `getBalance`, `getMrr` | | `meters` | `list`, `listAll`, `get`, `create`, `update`, `getCustomerBalance` | | `organizations` | `list`, `listAll`, `get`, `getMetrics`, `getRadarSettings`, `updateRadarSettings` | | `paymentLinks` | `list`, `listAll`, `get`, `create` | | `paymentRequests` | `list`, `listAll`, `get`, `create` | | `payouts` | `create`, `list`, `listAll`, `get` | | `products` | `list`, `listAll`, `get`, `create`, `addPrice`, `setDefaultPrice` | | `providers` | `list`, `listAll` | | `refunds` | `create`, `list`, `listAll`, `get` | | `riskAssessments` | `list`, `listAll`, `get` | | `settlements` | `findAll`, `findAllAll`, `findTransactions` | | `subscriptions` | `list`, `listAll`, `get`, `getUsage`, `cancel`, `resume`, `changePlan`, `update` | | `transactions` | `list`, `listAll`, `get` | | `usage` | `list`, `listAll`, `get`, `create`, `createSubscription`, `listPeriods`, `getRevenue`, `grantCredits`, `createEntitlement`, `checkEntitlement` | | `webhooks` | `list`, `listAll`, `get`, `create`, `update`, `delete`, `test`, `retryDelivery`, `listDeliveries`, `getDelivery`, `verifySignature` | *** ## TypeScript types Request and response bodies are fully typed from the OpenAPI schema: ```typescript import type { CreateSwitchCharge, SwitchChargeResponse, components, paths, } from '@lomi./sdk'; // DTO aliases (generated from components.schemas) type SwitchBody = CreateSwitchCharge; // Or reference paths directly type CheckoutCreate = paths['/checkout-sessions']['post']['requestBody']['content']['application/json']; ``` Database row types also ship for advanced integrations: ```typescript import type { Database } from '@lomi./sdk'; type Customer = Database['public']['Tables']['customers']['Row']; ``` *** ## Resources * [Getting Started](/start/create-account) * [API reference](/api) * [GitHub](https://github.com/lomiafrica/lomi.) * [Discord Community](https://discord.gg/yb4FnBmh) # CI/CD Source: https://docs.lomi.africa/build/reliability/ci-cd This guide covers best practices for integrating lomi. with your CI/CD pipeline, ensuring reliable deployments and automated testing. *** title: 'CI/CD' description: 'This guide covers best practices for integrating lomi. with your CI/CD pipeline, ensuring reliable deployments and automated testing.' ---------------------------------------------------------------------------------------------------------------------------------------------------- ## Environment setup ### Environment variables ```yaml filename="Example .env file for CI" # .env.ci LOMI_SECRET_KEY=lomi_sk_test_... # Use a dedicated test key LOMI_WEBHOOK_SECRET=whsec_... # Test webhook secret LOMI_API_URL=https://sandbox.api.lomi.africa # Point to sandbox LOMI_ENV=test ``` ### Secrets management Ensure your CI/CD environment securely loads required secrets. Never commit secrets directly to your repository. ```typescript filename="Loading secrets in Node.js" // config/secrets.ts export function loadSecrets(): void { const requiredSecrets = ['LOMI_SECRET_KEY', 'LOMI_WEBHOOK_SECRET']; for (const secret of requiredSecrets) { if (!process.env[secret]) { console.warn(`Warning: Missing recommended secret: ${secret}`); // Depending on your setup, you might throw an error: // throw new Error(`Missing required secret: ${secret}`); } } } // Call loadSecrets() early in your application or test setup ``` ## GitHub Actions ### Test workflow Run your integration tests against lomi. sandbox environment on pushes and pull requests. ```yaml filename=".github/workflows/test.yml" name: Test on: push: branches: [main, develop] # Trigger on main and develop branches pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 # Use latest version - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' # Use a current LTS version cache: 'npm' # Cache npm dependencies - name: Install dependencies run: npm ci - name: Run tests run: npm test env: LOMI_SECRET_KEY: ${{ secrets.LOMI_TEST_SECRET_KEY }} # Use GitHub secrets LOMI_WEBHOOK_SECRET: ${{ secrets.LOMI_TEST_WEBHOOK_SECRET }} LOMI_API_URL: 'https://sandbox.api.lomi.africa' LOMI_ENV: test ``` ### Deploy workflow Deploy your application using live keys only when pushing to your main branch. ```yaml filename=".github/workflows/deploy.yml" name: Deploy on: push: branches: [main] # Only trigger on pushes to main jobs: deploy: runs-on: ubuntu-latest environment: production # Optional: Define a GitHub environment for protection rules steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' # Example: Deploy to AWS (replace with your deployment provider) - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-1 # Your region - name: Install, Build, and Deploy run: | npm ci npm run build npm run deploy # Your deployment script env: LOMI_SECRET_KEY: ${{ secrets.LOMI_PROD_SECRET_KEY }} LOMI_WEBHOOK_SECRET: ${{ secrets.LOMI_PROD_WEBHOOK_SECRET }} LOMI_API_URL: 'https://api.lomi.africa' LOMI_ENV: production ``` ## Automated testing ### Pre-deployment checks Before deploying, run checks to ensure basic connectivity and configuration. ```typescript filename="Example pre-deployment check script" // scripts/pre-deploy-check.ts import { LomiSDK } from '@lomi./sdk'; // Assuming your SDK package name async function runPreDeploymentChecks(): Promise { console.log('Running pre-deployment checks...'); const apiKey = process.env.LOMI_SECRET_KEY; const apiUrl = process.env.LOMI_API_URL; if (!apiKey || !apiUrl) { throw new Error('LOMI_SECRET_KEY and LOMI_API_URL must be set'); } const lomi = new LomiSDK({ apiKey: apiKey, baseUrl: apiUrl, }); try { // 1. Verify API connectivity (e.g., fetch providers) console.log('Checking API connectivity...'); const providers = await lomi.providers.list(); if (!providers || providers.data.length === 0) { throw new Error('Failed to fetch providers or no providers available.'); } console.log(`Successfully fetched ${providers.data.length} providers.`); // 2. Optional: Test webhook endpoint connectivity if applicable // Note: The SDK might not have a direct webhook test method. // You might need a custom check or rely on integration tests. // Example placeholder: // console.log('Checking webhook endpoint...'); // const webhookTestResult = await checkMyWebhookEndpoint(); // if (!webhookTestResult.success) throw new Error('Webhook endpoint check failed'); console.log('Pre-deployment checks passed!'); } catch (error) { console.error('Pre-deployment check failed:', error); process.exit(1); // Exit with error code } } runPreDeploymentChecks(); ``` ### Integration tests Write tests that simulate user flows involving lomi. interactions. ```typescript filename="Example integration test (using Jest)" // tests/integration/payment.test.ts import { LomiSDK } from '@lomi./sdk'; // Assume setup/teardown logic exists elsewhere describe('Payment Integration', () => { let lomi: LomiSDK; beforeAll(() => { // Ensure required env vars are set for tests if (!process.env.LOMI_TEST_SECRET_KEY || !process.env.TEST_MERCHANT_ID) { throw new Error('Missing test environment variables'); } lomi = new LomiSDK({ apiKey: process.env.LOMI_TEST_SECRET_KEY, baseUrl: 'https://sandbox.api.lomi.africa', }); // await setupTestEnvironment(); // Your test setup }); it('should process a test payment end-to-end', async () => { // Create checkout session const sessionResponse = await lomi.checkoutSessions.create({ merchant_id: process.env.TEST_MERCHANT_ID!, // Use a test merchant ID amount: 1000, // Min amount for testing currency_code: 'XOF', success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', metadata: { test_order_id: `e2e_${Date.now()}` }, }); const sessionId = sessionResponse.data.checkout_session_id; expect(sessionId).toBeDefined(); // Simulate payment (requires a test helper or specific endpoint) // This part is highly dependent on lomi.'s testing capabilities // await lomi.testing.simulatePayment(sessionId, 'succeeded'); // Verify success (allow time for processing) // await new Promise(r => setTimeout(r, 5000)); // Wait if needed // const updatedSession = await lomi.checkoutSessions.get(sessionId); // expect(updatedSession.data.status).toBe('completed'); }, 30000); // Increase timeout for E2E test }); ``` ## Deployment strategies Choose a strategy that minimizes risk during deployment. ### Blue-green deployment Maintain two identical production environments (Blue and Green). Deploy to the inactive environment, test, then switch traffic. ```typescript filename="Conceptual blue-green deployment script" // scripts/deploy-blue-green.ts async function blueGreenDeploy(newVersion: string): Promise { const currentActive = await getActiveEnvironment(); // e.g., 'blue' const inactiveEnv = currentActive === 'blue' ? 'green' : 'blue'; // 1. Deploy to inactive environment console.log(`Deploying version ${newVersion} to ${inactiveEnv}...`); await deployToEnvironment(inactiveEnv, newVersion); // 2. Run health checks on inactive environment console.log(`Running health checks on ${inactiveEnv}...`); const health = await checkHealth(inactiveEnv); if (!health.ok) { console.error( `Health check failed for ${inactiveEnv}. Rolling back deployment.`, ); // await rollbackDeployment(inactiveEnv); // Optional rollback throw new Error('Health check failed on inactive environment'); } // 3. Switch traffic console.log(`Switching traffic to ${inactiveEnv}...`); await switchTraffic(inactiveEnv); // 4. Monitor the new active environment console.log(`Monitoring ${inactiveEnv}...`); // await monitorDeployment(inactiveEnv); // 5. Optional: Tear down the old environment after a period } ``` ### Canary deployment Gradually roll out the new version to a small subset of users/traffic before releasing it fully. ```typescript filename="Conceptual canary deployment script" // scripts/deploy-canary.ts async function canaryDeploy(newVersion: string): Promise { // 1. Deploy new version alongside current version with limited traffic (e.g., 10%) console.log(`Deploying canary version ${newVersion} with 10% traffic...`); await deployCanaryVersion(newVersion, '10%'); // 2. Monitor metrics (error rates, latency) for a defined period console.log('Monitoring canary metrics...'); const metricsOk = await monitorCanaryMetrics({ duration: '1h', errorThreshold: 0.05, }); if (!metricsOk) { console.error('Canary metrics failed. Rolling back canary...'); await rollbackCanary(newVersion); throw new Error('Canary deployment failed metrics check'); } // 3. Gradually increase traffic to the new version console.log('Canary metrics OK. Gradually increasing traffic...'); await scaleCanaryTraffic(newVersion, '50%'); await monitorCanaryMetrics({ duration: '30m' }); // Monitor again await scaleCanaryTraffic(newVersion, '100%'); // Full rollout // 4. Decommission the old version console.log('Canary deployment successful. Decommissioning old version...'); await decommissionOldVersion(); } ``` ## Monitoring Continuously monitor your integration health. ### Health checks Set up automated checks for API connectivity and critical functions. ```typescript filename="Conceptual health check function" // monitoring/healthCheck.ts import { LomiSDK } from '@lomi./sdk'; // import db from './database'; // Your database connection export async function checkServiceHealth(): Promise<{ status: string; errors: string[]; }> { const errors: string[] = []; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY!, baseUrl: process.env.LOMI_API_URL!, }); try { // Check lomi. API status (e.g., list providers) await lomi.providers.list(); } catch (error: any) { errors.push(`lomi. API check failed: ${error.message}`); } // try { // // Check Database health // await db.raw('SELECT 1'); // } catch (error: any) { // errors.push(`Database check failed: ${error.message}`); // } // Check essential internal services... return { status: errors.length === 0 ? 'healthy' : 'unhealthy', errors: errors, }; } ``` ### Metrics collection Track key metrics related to payments and webhooks. ```typescript filename="Conceptual metrics collection" // monitoring/metrics.ts export function collectMetrics(): Record { // Example metrics (implementation depends on your monitoring tools) return { // Payment metrics payments_processed_total: getTotalPayments(), payments_succeeded_rate: getSuccessRate(), payment_api_latency_ms: getAverageApiLatency(), // Webhook metrics webhooks_received_total: getTotalWebhooksReceived(), webhook_verification_failures_total: getWebhookVerificationFailures(), webhook_processing_latency_ms: getAverageWebhookProcessingTime(), // System metrics // system_error_rate: getErrorRate(), // system_cpu_usage: getCPUUsage(), }; } ``` ## Rollback procedures Have a plan to quickly revert to a previous stable version if a deployment introduces issues. ### Automated rollback Integrate rollback steps into your deployment workflow. ```typescript filename="Conceptual automated rollback script" // scripts/rollback.ts async function automaticRollback( failedDeploymentId: string, previousVersion: string, ): Promise { console.log( `Rolling back deployment ${failedDeploymentId} to version ${previousVersion}...`, ); try { // 1. Stop traffic to the failed version (if applicable) // await stopTraffic(failedDeploymentId); // 2. Redeploy the previous stable version await deployToEnvironment('production', previousVersion); // 3. Verify rollback success const health = await checkHealth('production'); if (!health.ok) { throw new Error('Rollback verification failed'); } console.log(`Rollback to ${previousVersion} successful and verified.`); // 4. Notify team // await notifyTeam(`Rollback completed for deployment ${failedDeploymentId}. Restored version ${previousVersion}.`); } catch (error: any) { console.error(`Automatic rollback failed: ${error.message}`); // await notifyTeam(`CRITICAL: Automatic rollback failed for deployment ${failedDeploymentId}. Manual intervention required.`); throw error; // Re-throw to signal CI failure } } ``` Testing guide Security best practices API reference # Error handling Source: https://docs.lomi.africa/build/reliability/error-handling When integrating with lomi., handle API errors so customers get a clear payment outcome and your logs stay useful. *** title: 'Error handling' description: "When integrating with lomi., handle API errors so customers get a clear payment outcome and your logs stay useful." --------------------------------------------------------------------------------------------------------------------------------- This page is integration advice: how to parse API errors in your app. The envelope, status codes, and stable `error.code` list live on [Errors](/api/errors). lomi. uses conventional HTTP response codes. Use `error.code` for branching, `error.message` for logs, and `request_id` when you contact support. ## Handling common error types The TypeScript SDK throws `LomiError` subclasses (`LomiValidationError`, `LomiAuthError`, `LomiNotFoundError`, `LomiRateLimitError`). There is no `LomiApiError` class. ### Validation errors (HTTP 400) These occur when the request data is invalid or missing required fields. ```typescript filename="Handling validation errors" import { LomiSDK, LomiValidationError, LomiError } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY! }); try { await lomi.checkoutSessions.create({ amount: -100, currency_code: 'INVALID', }); } catch (error) { if (error instanceof LomiValidationError) { console.error('Validation failed:', error.message, error.details); } else if (error instanceof LomiError) { console.error('API error:', error.statusCode, error.message); } } ``` ### Authentication errors (HTTP 401) These occur when the secret key is missing, invalid, or does not have the necessary permissions. ```typescript filename="Handling authentication errors" import { LomiSDK, LomiAuthError } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: 'invalid-key' }); try { await lomi.providers.list(); } catch (error) { if (error instanceof LomiAuthError) { console.error('Authentication failed:', error.message, error.requestId); } } ``` ### Rate limit errors (HTTP 429) These occur when you exceed the allowed number of requests. Default is 5000 requests per 15 minutes; money-moving writes are 120 per minute. See [Errors](/api/errors#rate-limits). ```typescript filename="Handling rate limit errors" import { LomiSDK, LomiRateLimitError } from '@lomi./sdk'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY! }); try { await lomi.providers.list(); } catch (error) { if (error instanceof LomiRateLimitError) { const details = error.body?.error?.details; const retryAfterSeconds = details && typeof details === 'object' && !Array.isArray(details) && typeof details.retry_after_seconds === 'number' ? details.retry_after_seconds : 60; console.warn('Rate limit exceeded. Retry after', retryAfterSeconds, 's'); } } ``` ### Server errors (HTTP 5xx) These indicate a problem on lomi.'s side. They should be rare. Retry after a delay. A `503` can also mean a muted rail such as `POST /charge/card`. ## Best practices 1. **Graceful degradation:** show a user-friendly message, not the raw API body. Log `request_id` on your server. 2. **Retry with exponential backoff and jitter** for network errors, `429`, and `5xx`. Do not retry other `4xx` responses until you fix the request. Pair retries with [idempotency keys](/build/reliability/idempotency-keys) on money-moving writes. 3. **Monitor:** track error rates in production and alert on spikes. ```typescript filename="Retries with exponential backoff" import { LomiError } from '@lomi./sdk'; async function withRetry( asyncFn: () => Promise, maxRetries = 3, initialDelayMs = 1000, ): Promise { let attempts = 0; while (true) { try { return await asyncFn(); } catch (error) { attempts++; const isRetryable = error instanceof LomiError && (error.statusCode === 429 || (error.statusCode ?? 0) >= 500); if (!isRetryable || attempts >= maxRetries) { throw error; } const delay = initialDelayMs * Math.pow(2, attempts - 1); const jitter = delay * 0.2 * Math.random(); const waitTime = Math.max(100, delay + jitter); await new Promise((resolve) => setTimeout(resolve, waitTime)); } } } ``` Idempotency keys Security best practices Error reference # Handling webhooks Source: https://docs.lomi.africa/build/reliability/handling-webhooks Webhooks provide real-time updates about events in your lomi. account. This guide explains how to securely receive and process these notifications. *** title: 'Handling webhooks' description: 'Webhooks provide real-time updates about events in your lomi. account. This guide explains how to securely receive and process these notifications.' ------------------------------------------------------------------------------------------------------------------------------------------------------------------ For a general introduction and setup guide, see [Setting up webhooks](/build/reliability). Operational behavior (retries, duplicate events, delivery logs): [Webhook reliability](/build/reliability/webhook-reliability). ## What you need * HTTPS endpoint that accepts `POST` with a JSON body * Webhook **signing secret** (`whsec_…`) from the dashboard, stored as `LOMI_WEBHOOK_SECRET`, not sent by lomi. on delivery * Middleware that preserves the **raw request body** for HMAC (see below) * Verify `X-Lomi-Signature` **before** parsing JSON or running auth middleware meant for your own API * Return **`200`** / **`204`** within a few seconds, then process the event asynchronously * Dedupe on top-level event `id` (duplicates are normal) Signature verification must use the **exact bytes** lomi. sent. Re-serializing JSON after `express.json()` changes spacing or key order and breaks HMAC. **Wrong:** `app.post('/webhook', express.json(), …)` then `JSON.stringify(req.body)` for the MAC. **Correct:** `app.post('/webhook', express.raw({ type: 'application/json' }), …)` and pass `req.body` (a `Buffer`) into your verifier, then `JSON.parse` only after the signature matches. Outbound webhook POSTs use **`X-Lomi-Signature`** (HMAC of the raw body with your endpoint’s `whsec_…` secret). lomi. does **not** send `X-API-Key` or `Authorization: Bearer` on delivery. If you see **401** in delivery logs, disable global API-key middleware on the webhook route, see [Signing secret vs Authorization](#webhook-signing-vs-authorization). ## Setup summary ### Configure your endpoint Ensure you have a dedicated HTTPS endpoint ready to receive POST requests with a JSON body. ```typescript filename="Basic Express setup for webhooks" import express from 'express'; import crypto from 'crypto'; const app = express(); // Define your webhook handling function async function handleWebhook(req: express.Request, res: express.Response) { const LOMI_WEBHOOK_SECRET = process.env.LOMI_WEBHOOK_SECRET; if (!LOMI_WEBHOOK_SECRET) { console.error('Webhook secret is not configured.'); return res.status(500).send('Webhook configuration error'); } // Verify signature (implementation below) const signature = req.headers['x-lomi-signature'] as string; if ( !signature || !verifySignature(req.body, signature, LOMI_WEBHOOK_SECRET) ) { return res.status(400).send('Invalid signature'); } // Respond quickly to acknowledge receipt res.status(200).json({ received: true }); // Process the event asynchronously const event = JSON.parse(req.body.toString()); try { await processWebhookEvent(event); } catch (error) { console.error('Error processing webhook event:', error); // Log the error, but don't fail the response to lomi. } } // Use express.raw() middleware to access the raw body for signature verification app.post( '/your-webhook-endpoint', express.raw({ type: 'application/json' }), handleWebhook, ); // Your signature verification function (see below) function verifySignature( payload: Buffer, signature: string, secret: string, ): boolean { // ... implementation ... return true; // Placeholder } // Your event processing logic async function processWebhookEvent(event: any): Promise { console.log(`Processing event: ${event.id}, Type: ${event.event}`); // Add your business logic here based on event.event } // Start the server... ``` ### Verify signatures Always verify the `X-Lomi-Signature` header to ensure the request is genuinely from lomi. and hasn't been tampered with. ```typescript filename="Webhook signature verification function" import crypto from 'crypto'; function verifySignature( payload: Buffer, // Raw request body (Buffer) signatureHeader: string, secret: string, ): boolean { if (!payload || !signatureHeader || !secret) { return false; } try { const hmac = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); // Use timing-safe comparison return crypto.timingSafeEqual( Buffer.from(signatureHeader), Buffer.from(hmac), ); } catch (error) { console.error('Error during signature verification:', error); return false; } } ```

Signing secret vs Authorization headers (avoid 401 failures)

Managing webhooks uses your **merchant API key** (`X-API-Key`), but **lomi. does not send that key (or `Authorization: Bearer …`) when delivering events to your URL.** For **outbound** webhook HTTP requests, expect only: | Header | Purpose | | ------------------ | ------------------------------------------------------------------------------------------- | | `Content-Type` | `application/json` | | `X-Lomi-Signature` | HMAC-SHA256 (**hex**) of the **raw** JSON body using this endpoint’s webhook signing secret | | `X-Lomi-Event` | Same value as top-level `"event"` in the JSON body | | `X-Lomi-Timestamp` | ISO-8601 instant when lomi. built the envelope (same value as the body `timestamp`) | | `User-Agent` | `lomi.-Webhook/1.0` | The **`whsec_…` signing secret** is for **you** to verify `X-Lomi-Signature` on the raw body, not a token lomi. places in `Authorization`. **Replay window (optional):** `X-Lomi-Timestamp` is informational. The HMAC still covers only the raw body, so you can keep existing verifiers unchanged. If you want to reject delayed replays, parse the header as ISO-8601 and drop requests older than **300 seconds**. Do not switch to a `t=…,v1=…` signed-payload scheme; that would break every shipped verifier. **If delivery logs show HTTP 401**, or a response body like `Authentication required`, that answer almost always comes **from your own server or proxy** (framework auth middleware, API gateway, Cloudflare Access, a catch-all Bearer rule, etc.) **before** your webhook handler runs. lomi. cannot satisfy a custom Bearer or API-key gate on POST. **What to change on your side:** expose a **dedicated** webhook route (path or subdomain) **without** global API-key middleware, verify `X-Lomi-Signature` first using the endpoint secret, then return `2xx`. Your other REST routes can still require Bearer or API keys normally. Related: delivery logs capture your endpoint’s HTTP status and response body; see [Webhooks](/api/webhooks). ## Processing events Once the signature is verified, you can safely process the event payload. ```typescript filename="Processing webhook events" interface LomiWebhookEvent { id: string; // UUID: use for idempotency/dedupe event: string; // e.g., 'PAYMENT_SUCCEEDED' timestamp: string; data: unknown; // Structure depends on the event type /** Mirrors API host `NODE_ENV` (e.g. `production`, `development`), not a checkout sandbox discriminator */ lomi_environment: string; } async function processWebhookEvent(event: LomiWebhookEvent): Promise { // Optional: Check if event ID has already been processed for idempotency if (await hasEventBeenProcessed(event.id)) { console.log(`Event ${event.id} already processed. Skipping.`); return; } console.log(`Processing event: ${event.id}, Type: ${event.event}`); switch (event.event) { case 'PAYMENT_SUCCEEDED': const transaction = event.data; // Contains transaction object console.log( `Payment succeeded for transaction: ${transaction.transaction_id}`, ); // Example: Fulfill order, grant access, update database // await fulfillOrder(transaction.metadata?.order_id, transaction); break; case 'PAYMENT_FAILED': const failedTxn = event.data; console.log( `Payment failed for transaction: ${failedTxn.transaction_id}`, ); // Example: Notify customer, update order status to failed // await handleFailedPayment(failedTxn.metadata?.order_id, failedTxn); break; case 'SUBSCRIPTION_CREATED': const subscription = event.data; console.log(`Subscription created: ${subscription.subscription_id}`); // Example: Provision service for new subscription break; case 'SUBSCRIPTION_RENEWED': const renewed = event.data; console.log(`Subscription renewed: ${renewed.subscription_id}`); // Example: Extend access for the new billing period break; case 'SUBSCRIPTION_CANCELLED': const cancelledSub = event.data; console.log(`Subscription cancelled: ${cancelledSub.subscription_id}`); // Example: Revoke access at period end or immediately break; // Add cases for other events you subscribe to... default: console.warn(`Unhandled event type: ${event.event}`); } // Optional: Mark event as processed await markEventAsProcessed(event.id); } // Placeholder functions for idempotency checks (implement with your database/cache) async function hasEventBeenProcessed(eventId: string): Promise { // Check your storage if eventId exists return false; // Replace with actual check } async function markEventAsProcessed(eventId: string): Promise { // Store eventId in your storage } ``` ## Best practices ### Respond quickly **Acknowledgment-first design:** Treat the webhook `POST` as a transport shim. Validate `X-Lomi-Signature`, persist or enqueue whatever you absolutely need so you cannot lose track of the envelope, then **`200`** / **`204`** back to lomi. immediately. Anything that touches external vendors, heavyweight SQL, or multi-step saga logic should execute **after** the HTTP lifecycle completes successfully from lomi.’s standpoint. Acknowledge webhook receipt by returning a `2xx` status code (e.g., `200`) **within a small number of seconds in the worst case**: but **aim for sub-second latency** routinely. Reason: each outbound POST from lomi. is guarded by **about a four-second** client read timeout; blowing that budget marks the delivery as failed for that round and may consume one of your limited automatic retries ([full matrix](/build/reliability/webhook-reliability)). Defer heavy work to **your own** queue afterward. ```typescript filename="Responding quickly and processing asynchronously" async function handleWebhook(req: express.Request, res: express.Response) { // ... (Verify signature) ... if (!isValidSignature) { return res.status(400).send('Invalid signature'); } // Acknowledge receipt immediately res.status(200).json({ received: true }); // Add event to a background queue for processing const event = JSON.parse(req.body.toString()); backgroundQueue.add('process-webhook', event); } ``` ### Handle duplicates (idempotency) Network issues, automatic retries, manual replays from the dashboard, and idempotency safeguards on lomi.’s side all mean you should expect **duplicate POSTs over time**. That is normal rather than exceptional, bake deduplication into your schema and workflows from day one rather than treating it as a rare bug. * **Check payload `id`:** Store the top-level `id` (UUID) of processed deliveries. Skip if already handled. * **Database Constraints:** Use unique constraints in your database where appropriate (e.g., on an order update based on the transaction ID) to prevent duplicate operations at the data layer. ```typescript filename="Idempotency check example" async function processWebhookEvent(event: LomiWebhookEvent): Promise { const isProcessed = await database.checkIfEventProcessed(event.id); if (isProcessed) { console.log(`Event ${event.id} is a duplicate, skipping.`); return; } // ... process the event ... await database.markEventAsProcessed(event.id); } ``` ### Error handling Implement robust error handling within your `processWebhookEvent` function. * **Log Errors:** Log detailed errors encountered during processing. * **Retry Logic (Internal):** For transient errors during processing (e.g., temporary database unavailability), consider internal retry logic within your background job handler. * **Monitoring:** Monitor your webhook endpoint for failures and your background queue for processing errors. * **Do Not Fail the `200 OK` Response:** Even if your internal processing fails later, ensure you have already sent the `200 OK` response to lomi.. lomi. only cares about the successful delivery acknowledgment. ### Logging Log key information for debugging: * Log receipt of webhook events (including the event ID and type). * Log the outcome of signature verification. * Log the start and end of event processing. * Log any errors during processing with relevant context (but avoid logging the full raw payload or sensitive data directly unless necessary and properly secured). ```typescript filename="Example logging within handler" async function handleWebhook(req: express.Request, res: express.Response) { const eventId = JSON.parse(req.body.toString())?.id || 'unknown'; console.log(`Received webhook request for event ID (potential): ${eventId}`); // ... (Verify signature) ... if (!isValidSignature) { console.warn(`Invalid signature for event ID: ${eventId}`); return res.status(400).send('Invalid signature'); } console.log(`Signature verified for event ID: ${eventId}`); res.status(200).json({ received: true }); // ... (Process asynchronously) ... } ``` ## Webhook events When creating or updating a webhook, you subscribe it to specific event types. lomi. only sends notifications for the events you subscribed to. | Event Enum | Description | Data payload type | | ------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------- | | `PAYMENT_CREATED` | A new payment attempt has been initiated. | `Transaction` | | `PAYMENT_SUCCEEDED` | A one-time payment is successful. | `Transaction` | | `PAYMENT_FAILED` | A one-time payment attempt failed. | `Transaction` | | `SUBSCRIPTION_CREATED` | A new subscription is created. | `Subscription` | | `SUBSCRIPTION_UPDATED` | A subscription changed (pause, resume, plan change, cancel scheduled, etc.). | `Subscription` (with `previous_attributes`) | | `SUBSCRIPTION_RENEWED` | A subscription successfully renews. | `Subscription` | | `SUBSCRIPTION_CANCELLED` | A subscription is cancelled or expired. | `Subscription` | | `REFUND_COMPLETED` | A refund is successfully processed (Wave, MTN, cards, π-SPI, and manual). | `Refund` | | `REFUND_FAILED` | A refund attempt failed. | `Refund` | | `REFUND_CREATED` | A refund attempt was created. | `Refund` | | `PAYOUT_CREATED` | A payout or withdrawal was created (`pending`). | `Payout` | | `PAYOUT_COMPLETED` | A payout reached `completed`. | `Payout` | | `PAYOUT_FAILED` | A payout reached `failed`. | `Payout` | | `DISPUTE_CREATED` | A card dispute was opened. | `Dispute` | | `DISPUTE_UPDATED` | Dispute status or evidence changed. | `Dispute` | | `DISPUTE_CLOSED` | A dispute reached a terminal state. | `Dispute` | | `test.webhook` | A test event generated via the API. | `TestPayload` | **Renewal payment failures** emit **`PAYMENT_FAILED`** on the failed transaction. There is no separate subscription payment-failed webhook. Use **`SUBSCRIPTION_RENEWED`** for successful billing cycles. See [Subscriptions](/build/billing/subscriptions) and [Checkout behavior: Subscription checkout](/build/accept/checkout-behavior#subscription-checkout). ### `SUBSCRIPTION_UPDATED` payload Non-terminal subscription changes use a structured event envelope: current state in `object`, what changed in `previous_attributes`, and optional `context` for debugging. ```json filename="Example webhook payload (SUBSCRIPTION_UPDATED)" { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "SUBSCRIPTION_UPDATED", "timestamp": "2026-06-11T14:30:00.000Z", "data": { "object": { "subscription_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "status": "paused", "price_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "cancel_at_period_end": false, "next_billing_date": "2026-07-01", "plan_name": "Pro Monthly", "billing_interval": "month" }, "previous_attributes": { "status": "active" }, "context": { "source": "merchant_api", "actor": "merchant" } } } ``` Reconcile entitlements from `data.object`. Use `previous_attributes` to branch logic (for example plan upgrade vs pause). `context.source` is `merchant_api`, `customer_portal`, or `cron`. Webhook subscriptions and JSON payloads use **SCREAMING\_SNAKE\_CASE** event names (for example `PAYMENT_SUCCEEDED`). Some docs or OpenAPI tooling may show dotted names (for example `payment.succeeded`) as a human-readable alias for the same logical event. Use the enum values above when configuring endpoints and when comparing `event` in request bodies. For retries, idempotency, and delivery logs, see [Webhook reliability](/build/reliability/webhook-reliability). ## Testing webhooks You can test endpoints with the API, `curl`, the [Testing guide](/build/reliability/testing), or the CLI. ### Create a webhook for testing Point the endpoint at a test receiver such as [Webhook.site](https://webhook.site/) or a local tunnel like ngrok. ```bash filename="Terminal" curl -X POST "https://api.lomi.africa/webhooks" \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{ "url": "YOUR_TEST_WEBHOOK_URL", "authorized_events": ["PAYMENT_SUCCEEDED", "test.webhook"], "description": "Test Endpoint" }' ``` Note the `id` and `secret` from the response. ### Send a test event Use `POST /webhooks/{id}/test` to send a predefined `test.webhook` event. ```bash filename="Terminal" curl -X POST "https://api.lomi.africa/webhooks/YOUR_WEBHOOK_ID/test" \ -H "X-API-Key: YOUR_API_KEY" ``` Confirm the event arrived and verify the signature with the stored secret. ## Monitoring Use lomi. Dashboard (**Developers -> Webhooks**) to monitor delivery attempts, view recent events, check response codes from your endpoint, and manually retry failed deliveries. Security best practices Error handling API reference # Idempotency keys Source: https://docs.lomi.africa/build/reliability/idempotency-keys Idempotency ensures that an API request, if retried due to a network error or timeout, won't accidentally be performed multiple times. This is crucial for operations like creating payments or refunds to prevent duplicate actions. *** title: "Idempotency keys" description: "Idempotency ensures that an API request, if retried due to a network error or timeout, won't accidentally be performed multiple times. This is crucial for operations like creating payments or refunds to prevent duplicate actions. " - ## How it works When you make a potentially mutating API request (like `POST`, `PATCH`, `DELETE`), you can include a unique `Idempotency-Key` in the request header. 1. **Generate a Unique Key:** Create a unique string (e.g., a UUID) for each distinct operation you want to perform. This key represents the *intent* to perform the operation. 2. **Include the Header:** Send the key in the `Idempotency-Key` header with your request. 3. **First Request:** lomi. processes the request normally and stores the result associated with your key. 4. **Subsequent Requests (Same Key):** If lomi. receives another request with the **same key** within 24 hours, it will not re-process the operation. Instead, it will return the **same response** (success or error) that it sent for the original request. `Idempotency-Key` is **required** on money-moving writes: `POST /payment-requests`, `POST /refunds`, `POST /payouts`, `POST /settlements/instant`, and `POST /charge/*`. Missing keys return `400` `idempotency_key_required`. `POST /checkout-sessions` accepts an optional key (recommended for retries). Creating a hosted checkout page does not capture funds. When the response is served from the idempotency cache, the API adds `Idempotency-Cache-Hit: true`. Same key with a different body returns `409` `idempotency_key_reused`. An in-flight duplicate returns `409` `idempotency_in_progress` (retry with the same key). ## Using idempotency keys Include the `Idempotency-Key` header in your API calls. Most lomi. SDKs provide a way to pass this easily. ```typescript filename="Using idempotency key with Node.js SDK" import { LomiSDK } from '@lomi./sdk'; import { v4 as uuidv4 } from 'uuid'; const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY! }); // Generate a unique key *before* making the request const idempotencyKey = uuidv4(); // e.g., 'f47ac10b-58cc-4372-a567-0e02b2c3d479' try { const session = await lomi.checkoutSessions.createCheckoutSession( { merchant_id: 'your_merchant_id', amount: 1000, currency_code: 'XOF', success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', // ... other params }, { // Pass the key in the request options idempotencyKey: idempotencyKey, }, ); console.log( 'Checkout session created/retrieved:', session.data.checkout_session_id, ); // Store the key and the result (session ID) associated with your operation } catch (error) { console.error('Failed to create checkout session:', error); // Handle the error } ``` ```typescript filename="Using idempotency key with direct API calls (fetch)" import { v4 as uuidv4 } from 'uuid'; const idempotencyKey = uuidv4(); const apiKey = process.env.LOMI_SECRET_KEY; const apiUrl = 'https://api.lomi.africa/checkout-sessions'; // Use correct endpoint async function createSessionDirectly() { try { const response = await fetch(apiUrl, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ merchant_id: 'your_merchant_id', amount: 1000, currency_code: 'XOF', success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', // ... other params }), }); const data = await response.json(); if (!response.ok) { // Handle API error (response.status, data.error) console.error(`API Error (${response.status}):`, data.error); return; } console.log( 'Checkout session created/retrieved:', data.data.checkout_session_id, ); } catch (networkError) { console.error('Network error during request:', networkError); // Implement retry logic here if appropriate, reusing the SAME idempotencyKey } } createSessionDirectly(); ``` ## Generating keys Keys must be unique for each distinct operation. * **UUIDs:** Recommended approach. Generate a standard UUID v4 for each request attempt. ```typescript import { v4 as uuidv4 } from 'uuid'; const key = uuidv4(); ``` * **Deterministic Keys:** You can create keys based on unique aspects of the operation (e.g., user ID + order ID + timestamp), but ensure the resulting key is truly unique for each *intended* operation. Be cautious about collisions. ## Key lifecycle * lomi. stores idempotency keys and their corresponding responses for **24 hours**. * After 24 hours, a key expires and can potentially be reused, although using unique keys like UUIDs is generally safer. * Sending a request with a key that was used within the last 24 hours for a *different* request body might result in an error. ## Error handling If you retry a request with an idempotency key that was already successfully processed, you'll receive the original successful response (HTTP `200 OK`). If the original request failed (e.g., HTTP `400 Bad Request`), retrying with the same key will return the original error response. You won't be able to fix the request by simply retrying with the same key. Specific idempotency-related errors (though less common if using UUIDs correctly): * **`409` `idempotency_key_reused`:** the key was reused with a different request body within 24 hours. * **`409` `idempotency_in_progress`:** a request with this key is still running. Retry with the same key. * **`400` `idempotency_key_required`:** a money-moving write omitted the header. ## Best practices 1. **Use for Mutating Requests:** Primarily use idempotency keys for `POST`, `PATCH`, and `DELETE` requests where duplicate operations could cause issues. 2. **Generate Keys Correctly:** Use a robust method like UUID v4 to ensure uniqueness for each operation attempt. 3. **Retry Network Errors:** When a request fails due to a network error or timeout (where you don't receive a definitive success or failure from lomi.), retry the request using the **exact same idempotency key**. 4. **Don't Retry Client Errors:** Do not retry requests that failed with a `4xx` status code (other than potentially `429 Rate Limit Exceeded`) using the same idempotency key. Fix the underlying issue in the request first and generate a *new* key for the corrected request. 5. **Store Keys (Optional):** You might store the idempotency key alongside your internal record of the operation (e.g., in your order database) to aid in tracking and debugging retries. ## Common scenarios * **Creating Payments/Checkout sessions:** Essential to prevent charging a customer twice if your initial request times out but was actually processed by lomi. * **Creating Refunds:** Prevents accidentally refunding a transaction multiple times. * **Creating Customers/Products/Webhooks:** Useful to avoid creating duplicate resources due to retries. Error handling Handling webhooks API reference # Reliability Source: https://docs.lomi.africa/build/reliability Webhooks, payment verification, testing, and the operational practices that keep an integration safe in production. *** index: true title: 'Reliability' description: 'Webhooks, payment verification, testing, and the operational practices that keep an integration safe in production.' ---------------------------------------------------------------------------------------------------------------------------------- Use this folder for the operational layer of a lomi. integration: receive events, confirm payments, and test failure paths. * **[Handling webhooks](/build/reliability/handling-webhooks):** receive, verify, and subscribe to events. * **[n8n webhook recipe](/build/reliability/n8n-webhooks):** verify `X-Lomi-Signature` in n8n and send WhatsApp or email. * **[Webhook reliability](/build/reliability/webhook-reliability):** retries, duplicates, timeouts, and delivery logs. * **[Verify payments](/build/reliability/verify-payments):** confirm status with the API and webhooks, never from the browser redirect alone. * **[Payment lifecycle](/build/reliability/payment-lifecycle):** how money-in and money-out statuses connect. * **[Testing](/build/reliability/testing):** how to structure sandbox and production tests. * **[Simulate errors](/build/reliability/simulate-errors):** the scenario matrix for declines, pending Mobile Money, and webhook failures. Test credentials and card numbers live on [Sandbox payments](/start/sandbox-payments). Error codes live on [Errors](/api/errors). # n8n webhook recipe Source: https://docs.lomi.africa/build/reliability/n8n-webhooks Receive a signed lomi. PAYMENT_SUCCEEDED event in n8n and send a WhatsApp or email confirmation. *** title: 'n8n webhook recipe' description: 'Receive a signed lomi. PAYMENT\_SUCCEEDED event in n8n and send a WhatsApp or email confirmation.' ---------------------------------------------------------------------------------------------------------------- This is the no-code path for the signed-event contract: lomi. POSTs to n8n, you verify `X-Lomi-Signature` on the raw body, then notify the customer. The same pattern works for `REFUND_COMPLETED` once you subscribe to that event. You do not need a custom server. n8n is the listener. ## What you need * An n8n Cloud or self-hosted instance * A lomi. webhook signing secret (`whsec_…`) from the dashboard * A WhatsApp or email node (Twilio, WhatsApp Cloud, Resend, or SMTP) ## 1. Create the webhook in lomi. 1. Dashboard → Developers → Webhooks → Create. 2. URL: your n8n **Webhook** node production URL (`https:///webhook/`). 3. Subscribe to `PAYMENT_SUCCEEDED` (add `REFUND_COMPLETED` if you reverse fulfillment on refunds). 4. Copy the `whsec_…` secret. Store it as an n8n credential or environment variable. Never put it in a public workflow JSON. ## 2. Webhook node in n8n * Method: `POST` * Response: `Immediately` with HTTP `200` so lomi. does not retry while you send WhatsApp * Keep the **raw body**. Do not enable JSON parse before signature check. ## 3. Verify the signature HMAC-SHA256 **hex** of the **raw** body with the endpoint secret. Compare to `X-Lomi-Signature`. Optional: if `X-Lomi-Timestamp` (ISO-8601, same as the body `timestamp`) is more than 300 seconds old, stop the workflow (replay protection). Do not change the HMAC input to include the timestamp; the signature is body-only. n8n Code node example: ```javascript const crypto = require('crypto'); const secret = $env.LOMI_WEBHOOK_SECRET; const raw = $input.first().json.bodyRaw ?? JSON.stringify($input.first().json); const expected = crypto.createHmac('sha256', secret).update(raw).digest('hex'); const signature = $input.first().json.headers['x-lomi-signature']; if (expected !== signature) { throw new Error('Invalid lomi. webhook signature'); } return $input.all(); ``` If your Webhook node already parsed JSON, switch it to binary/raw mode or the HMAC will not match. ## 4. Send WhatsApp or email From the envelope `data` object (a `Transaction` for `PAYMENT_SUCCEEDED`): * Amount and currency * Customer phone or email * `transaction_id` for support Map those fields into your WhatsApp template or email body. Dedupe on envelope `id` so a retry does not message twice. ## 5. Test Use **Send test** in the dashboard, or complete a sandbox charge. Delivery logs should show `200`. If you see `401`, n8n (or a proxy in front of it) is requiring an API key; lomi. only sends `X-Lomi-Signature`, `X-Lomi-Event`, and `X-Lomi-Timestamp`. Details: [Handling webhooks](/build/reliability/handling-webhooks). # Payment and payout lifecycle Source: https://docs.lomi.africa/build/reliability/payment-lifecycle How money-in and money-out statuses connect, when pending payments expire, and how completion credits your balance. *** title: 'Payment and payout lifecycle' description: 'How money-in and money-out statuses connect, when pending payments expire, and how completion credits your balance.' docType: explanation -------------------- import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; Use this page as the merchant-facing map for payment and payout status. ## Money in (payments) ```mermaid stateDiagram-v2 [*] --> pending: live payment initiated [*] --> completed: test payment (sandbox) pending --> completed: provider confirms success pending --> failed: provider decline or hard failure pending --> expired: timeout or session expiry completed --> refunded: refund processed completed --> held: platform risk hold held --> completed: hold released held --> refunded: refund processed failed --> [*] expired --> [*] refunded --> [*] ``` In **test**, new payments often start as `completed` immediately. In **live**, they typically start `pending` until the customer approves mobile money or the card issuer confirms. ### Transaction statuses | Status | Meaning | | ----------- | ----------------------------------------------------------------------------------------------------------------- | | `pending` | Payment initiated; final outcome not yet confirmed (typical in **live**). Do not fulfill. | | `completed` | Payment succeeded. Credit test or live balance; fulfill after [verification](/build/reliability/verify-payments). | | `held` | Captured payment frozen by the platform. Not withdrawable and excluded from revenue until released or refunded. | | `failed` | Payment did not succeed. Release inventory; offer retry. | | `expired` | Pending payment timed out. Release inventory; offer retry. | | `refunded` | Refund processed against the original payment. Reverse fulfillment if applicable. | ### Provider-facing status mapping Platform `transaction_status` is reflected in `provider_payment_status` on linked provider records: | Transaction status | Provider payment status | | ------------------ | ----------------------- | | `completed` | `succeeded` | | `held` | `succeeded` | | `pending` | `processing` | | `failed` | `cancelled` | | `expired` | `expired` | | `refunded` | `refunded` | See [Transactions](/build/money/transactions) for field names in API responses. ### Pending expiration Pending transactions can expire automatically: * By **age** (older than the platform TTL). * Optionally by **provider session expiry** (for example Wave `when_expires`). Expired rows carry `expiration_info` in metadata describing why they closed. ### What happens on `completed` Completion is idempotent. A duplicate completion does not credit the balance twice: metadata is merged, but the balance is credited once. * Coupon usage may be incremented. * Subscriptions linked to the transaction may move from `pending` to `active`. For balance timing, see [Balance and settlement](/build/money/balance-and-settlement). ## Money out (payouts and refunds) Payouts move through `pending` → `processing` → `completed` or `failed`. Treat **`completed`** as “funds left the platform” for withdrawals and beneficiary payouts. ### Withdrawals vs beneficiaries * **Withdrawals**: funds from your lomi. balance to your own payout method (mobile money, bank, and similar). * **Beneficiary payouts**: funds sent to a third-party account (vendors, partners, or refund routing). Some flows **validate balance at creation** and **debit or finalize on completion**. A payout can be accepted while still processing. If the balance changes before final settlement, **completion can fail** and the payout may be marked failed or reversed according to provider rules. ### Fees and limits Payout fees depend on organization fee configuration (including tiered overrides), provider and payment method (for example local vs international bank), and currency. Withdrawal flows can enforce minimum/maximum amounts and per-period caps. Exact numbers are configured per organization and provider. See [Pricing](/start/merchant-of-record/pricing). ### Failure and retries Failures may occur due to invalid account details, provider rejection, or insufficient balance at settlement time. Check payout status and metadata in the API. See [Payouts](/build/money/payouts) and [Refunds](/build/money/refunds). ## Balances and settlement Completed payments update your merchant balance according to fee rules and availability windows. See [Balance and settlement](/build/money/balance-and-settlement). ## Webhooks tie it together Configure webhooks so your system reacts when status changes without polling: 1. [Setting up webhooks](/build/reliability) 2. [Handling webhooks](/build/reliability/handling-webhooks) 3. [Webhook reliability](/build/reliability/webhook-reliability) Checkout behavior Simulate errors Transactions # Security best practices Source: https://docs.lomi.africa/build/reliability/security-best-practices Report a vulnerability, then lock down API keys, TLS, and webhook verification for your lomi. integration. *** title: 'Security best practices' description: 'Report a vulnerability, then lock down API keys, TLS, and webhook verification for your lomi. integration.' ------------------------------------------------------------------------------------------------------------------------- ## Report a vulnerability If you believe you have found a security vulnerability in lomi., please disclose it privately. Do not open a public GitHub issue for security reports. **Preferred channel:** [GitHub Security Advisory](https://github.com/lomiafrica/lomi./security/advisories/new) (private), or the form below. Include: * Description and impact * Steps to reproduce * Affected URLs or components (for example `api.lomi.africa`, checkout, dashboard) * Proof of concept if available (no real customer payment data) We aim to acknowledge reports within **3 business days** and share a remediation timeline based on severity. You can also email [security@lomi.africa](mailto:security@lomi.africa). Machine-readable contact details are at [/.well-known/security.txt](/.well-known/security.txt). ### Scope In scope: lomi.-operated services (API, checkout, dashboard, admin, MCP, Supabase Postgres/Auth/Storage, customer portal, docs, website). Out of scope unless we agree in writing: * Third-party provider consoles and infrastructure * Merchant-customized plugin deployments on merchant infrastructure * Social engineering and physical attacks * Denial-of-service without prior written authorization ### Safe harbor We support good-faith research that follows this policy. Do not access, modify, or delete data belonging to other users. Use sandbox environments and test accounts where possible. ### Severity and response targets | Severity | Examples | Target | | -------- | --------------------------------------------------------------------- | ---------------------------------------------------- | | Critical | Auth bypass on admin/provisioning, exposure of sensitive payment data | Mitigation as soon as possible, update within 7 days | | High | Tenant isolation flaw, webhook signature bypass | Fix within 30 days | | Medium | XSS with limited impact, misconfiguration | Fix within 90 days | | Low | Informational | Best-effort backlog | ### Recognition We thank researchers who help keep lomi. and our merchants safe. Coordinated disclosure may be acknowledged in release notes with permission. ## API authentication ### API key security 1. **Secure Storage:** **Never** hardcode API keys (`LOMI_SECRET_KEY`) in your source code or commit them to version control. Use environment variables or a secure secrets management service. ```typescript filename="Using environment variables for API key" import { LomiSDK } from '@lomi./sdk'; // Don't hardcode API keys const lomi = new LomiSDK({ apiKey: process.env.LOMI_SECRET_KEY, // Loaded from environment }); ``` 2. **Key Rotation:** Rotate your API keys periodically through lomi. Dashboard. If a key is compromised, revoke it immediately and generate a new one. 3. **Environment Separation:** Strictly use Test keys (`lomi_sk_test_...`) for development and testing environments and Live keys (`lomi_sk_live_...`) only for your production environment. 4. **Access Control:** Limit access to your API keys within your organization and infrastructure to only those systems and personnel that require it. ## Request security ### TLS requirements All communication with lomi.. API **must** be over HTTPS (TLS 1.2 or higher) to encrypt data in transit. Ensure your HTTP clients enforce TLS. ```typescript filename="Ensuring HTTPS base URL" import axios from 'axios'; const LOMI_API_URL = 'https://api.lomi.africa'; // Always use HTTPS const apiClient = axios.create({ baseURL: LOMI_API_URL, headers: { Authorization: `Bearer ${process.env.LOMI_SECRET_KEY}`, 'Content-Type': 'application/json', }, }); ``` ### Request validation Validate data on your server **before** sending it to lomi.. API. 1. **Input Sanitization:** Sanitize user inputs to prevent injection attacks (though lomi. also performs validation, defense-in-depth is recommended). ```typescript filename="Basic input sanitization examples" function sanitizeAmount(input: any): number | null { const num = Number(input); // Ensure it's a positive integer (adjust if decimals are needed) return Number.isInteger(num) && num > 0 ? num : null; } function sanitizePhoneNumber(phone: string | undefined): string | null { // Basic example: Remove non-digits, check length (adapt for specific formats) const digits = phone?.replace(/\D/g, ''); return digits && digits.length >= 8 ? digits : null; // Adjust length check } ``` 2. **Schema Validation:** Use libraries like Zod or Joi to validate the structure and types of data before making API calls. ```typescript filename="Using Zod for schema validation" import { z } from 'zod'; const CheckoutSessionInputSchema = z.object({ amount: z.number().positive('Amount must be positive'), currency_code: z.enum(['XOF']), // Adjust allowed currencies merchant_id: z.string().uuid('Invalid merchant ID'), success_url: z.string().url('Invalid success URL'), cancel_url: z.string().url('Invalid cancel URL'), // Add other fields and validations... }); function validateCheckoutRequest(data: unknown) { return CheckoutSessionInputSchema.safeParse(data); } ``` ## Webhook security Refer to the [Setting up webhooks](/build/reliability) and [Handling webhooks](/build/reliability/handling-webhooks) guides for detailed instructions. ### Signature verification **Always** verify the `X-Lomi-Signature` header on incoming webhook requests using your endpoint's unique `Signing Secret` (`LOMI_WEBHOOK_SECRET`). This prevents attackers from sending malicious or fake events to your endpoint. ```typescript filename="Webhook signature verification (Node.js/Express)" import crypto from 'crypto'; import express from 'express'; const LOMI_WEBHOOK_SECRET = process.env.LOMI_WEBHOOK_SECRET; // Ensure express.raw() is used for the webhook route // app.post('/your-webhook-endpoint', express.raw({ type: 'application/json' }), (req, res) => { ... }); function verifyWebhookSignature( rawBody: Buffer, signatureHeader: string | undefined, secret: string, ): boolean { if (!rawBody || !signatureHeader || !secret) { return false; } try { const hmac = crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signatureHeader), Buffer.from(hmac), ); } catch (error) { console.error('Signature verification error:', error); return false; } } ``` ### Webhook endpoint best practices 1. **HTTPS:** Use only HTTPS endpoints for receiving webhooks. 2. **Access Control:** If possible, consider restricting access to your webhook endpoint (e.g., IP whitelisting, although lomi. IP addresses might change). 3. **Quick Response & Asynchronous Processing:** Respond immediately with a `200 OK` and process the event asynchronously to avoid timeouts. 4. **Rate Limiting:** Apply rate limiting to your webhook endpoint to prevent abuse. 5. **Error Handling:** Handle errors gracefully during processing, log issues, but always return `200 OK` to lomi. if the signature was valid. ## Data security ### Sensitive data handling 1. **Data Minimization:** Only collect and send the data necessary for the transaction via lomi. API. Avoid sending unnecessary sensitive customer information. ```typescript filename="Minimizing data in metadata" // Good: Use internal IDs const metadata = { order_id: 'internal-order-123' }; // Avoid: Sending PII unless absolutely necessary and handled correctly // const badMetadata = { user_password: '...', full_address: '...' }; const session = await lomi.checkoutSessions.createCheckoutSession({ // ... other params metadata: metadata, }); ``` 2. **Secure Storage:** Avoid storing sensitive payment details (like full phone numbers used for payment confirmation if captured) unless absolutely necessary and compliant with security standards like PCI DSS (though lomi. handles the core PCI compliance for payment processing). Encrypt sensitive data at rest and implement strict access controls. ### Error logging Be cautious when logging errors. Avoid logging sensitive information like API keys, webhook secrets, or full customer data in your logs. ```typescript filename="Sanitizing logs" // Avoid logging sensitive data function logError(error: Error, context?: Record) { const sanitizedContext = { ...context }; // Redact sensitive fields if they exist in context if (sanitizedContext?.apiKey) sanitizedContext.apiKey = '[REDACTED]'; if (sanitizedContext?.customerPhone) sanitizedContext.customerPhone = '[REDACTED]'; if (sanitizedContext?.rawBody) sanitizedContext.rawBody = '[REDACTED]'; console.error('Error occurred:', { message: error.message, stack: error.stack, // Be cautious about stack traces in production logs context: sanitizedContext, }); } ``` ## Network security ### Rate limiting Implement rate limiting on your own API endpoints that interact with lomi. to prevent abuse and control costs. ```typescript filename="Rate limiting Express API routes" import rateLimit from 'express-rate-limit'; const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per window standardHeaders: true, // IETF RateLimit / RateLimit-Policy legacyHeaders: true, // Also emit X-RateLimit-* for existing clients }); // Apply to your API routes app.use('/api/', apiLimiter); ``` ### Timeouts Set reasonable timeouts for requests made to lomi.. API to prevent your application from hanging indefinitely. ```typescript filename="Setting timeouts with Axios" import axios from 'axios'; const apiClient = axios.create({ baseURL: 'https://api.lomi.africa', timeout: 15000, // 15 seconds timeout headers: { Authorization: `Bearer ${process.env.LOMI_SECRET_KEY}` }, }); ``` ## Monitoring and alerts 1. **Activity Monitoring:** Monitor API usage patterns, payment success rates, and error rates. Use monitoring tools (e.g., Datadog, Sentry, Prometheus/Grafana) to track these metrics. 2. **Suspicious Activity Alerts:** Set up alerts for: * High rates of failed API requests. * Failed webhook signature verifications. * Unexpected spikes or drops in transaction volume. * Attempts to use invalidated API keys. ## Development practices 1. **Code Security:** * Keep SDKs and libraries (especially crypto libraries) up-to-date. * Use security linters (e.g., ESLint security plugins). * Perform regular code reviews focusing on security aspects. * Sanitize all external inputs. 2. **Environment Separation:** Maintain separate configurations (API keys, webhook secrets) for development, staging, and production environments. ```typescript filename="Environment-specific configuration" const config = { development: { apiUrl: 'https://sandbox.api.lomi.africa', apiKey: process.env.LOMI_TEST_SECRET_KEY, webhookSecret: process.env.LOMI_TEST_WEBHOOK_SECRET, }, production: { apiUrl: 'https://api.lomi.africa', apiKey: process.env.LOMI_PROD_SECRET_KEY, webhookSecret: process.env.LOMI_PROD_WEBHOOK_SECRET, }, }[process.env.NODE_ENV || 'development']; const lomi = new LomiSDK({ apiKey: config.apiKey, baseUrl: config.apiUrl }); const webhookSecret = config.webhookSecret; ``` Error handling Idempotency keys API reference # How do I simulate errors? Source: https://docs.lomi.africa/build/reliability/simulate-errors Test declines, authentication challenges, async mobile money, and webhook failures before go-live. *** title: 'How do I simulate errors?' description: 'Test declines, authentication challenges, async mobile money, and webhook failures before go-live.' docType: how-to --------------- import { Callout } from '@/components/docs/docs-callout'; import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; Use this index to exercise failure paths in **sandbox**. This page is the **scenario matrix**. Full test card numbers and MoMo behavior live in **[Sandbox payments](/start/sandbox-payments)**. How to structure tests: **[Testing guide](/build/reliability/testing)**. Run these scenarios only with **test API keys** (`lomi_sk_test_...`). Never use test card numbers in live mode. ## Scenario matrix | Scenario | How to trigger | Expected outcome | Learn more | | -------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------- | | Card approved | `4242 4242 4242 4242` on hosted checkout | `completed`, test balance credit | [Sandbox payments: cards](/start/sandbox-payments#testing-card-payments) | | Card declined | Decline test PANs in sandbox table | `failed` | Same section | | 3D Secure / authentication | Auth-required test cards | `requires_action` or challenge UI | [Sandbox payments](/start/sandbox-payments) | | Wave (test) | Select Wave on sandbox checkout | Often immediate `completed` | [Wave](/build/payment-methods/wave) | | MTN (test) | `POST /charge/mtn` with test MSISDN | Immediate `completed` on test key | [MTN](/build/payment-methods/mtn-momo) | | Direct MoMo `pending` (test) | `X-Scenario-Key: pending` on `POST /charge/mtn` or `/charge/wave` | Charge stays `PENDING`; no auto-complete | [Sandbox payments: scenarios](/start/sandbox-payments#testing-mobile-money) | | Direct MoMo `failed` (test) | `X-Scenario-Key: failed` on `POST /charge/mtn` or `/charge/wave` | `400` error response | Same section | | MTN (live) | Real MSISDN, live key | Starts `PENDING` | [Verify payments](/build/reliability/verify-payments) | | Pending expiration | Wait beyond session/transaction TTL | `expired` or `failed` | [Payment and payout lifecycle](/build/reliability/payment-lifecycle) | | Webhook signature failure | POST without valid `X-Lomi-Signature` | Your endpoint returns 4xx; lomi. retries | [Handling webhooks](/build/reliability/handling-webhooks) | | Duplicate webhook | Replay same event `id` | Your handler dedupes; no double fulfill | [Webhook reliability](/build/reliability/webhook-reliability) | | Insufficient balance payout | Test payout rules in sandbox | `400` or `failed` payout | [Payouts in test mode](/start/sandbox-payments#payouts-in-test-mode) | | Invalid API key | Omit or wrong `X-API-Key` | `401` | [Authentication](/api/authentication) | | Rate limit | Burst requests in test | `429` | [Error handling](/build/reliability/error-handling) | | Card 3DS via `X-Scenario-Key` | Not supported | Use test PANs | [Sandbox payments](/start/sandbox-payments#testing-card-payments) | | MoMo redirect vs push via header | Not supported | Wave is a launch URL; MTN is a push | [Mobile money](/build/mobile-money) | | Payout `insufficient_balance` via `X-Scenario-Key` | Not supported | Follow payout test rules (Wave is live-only) | [Payouts in test mode](/start/sandbox-payments#payouts-in-test-mode) | | Open a dispute in sandbox | Not supported | Live card-network events only | [Disputes](/build/money/disputes) | ## Direct charge scenarios (test key only) On **`POST /charge/mtn`** and **`POST /charge/wave`**, send `X-Scenario-Key` to override default test behavior. This applies to **direct charges only**-hosted checkout and card charges do not read this header yet. | Header value | Behavior | | ------------ | ------------------------------------------------------------ | | *(omit)* | Default: test charge auto-completes and credits test balance | | `pending` | Charge remains `PENDING`; no auto-complete (poll or webhook) | | `failed` | Request fails with `400` | ```bash # Keep charge pending (CI / dunning tests) curl -sS -X POST "https://sandbox.api.lomi.africa/charge/mtn" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "X-Scenario-Key: pending" \ -H "Content-Type: application/json" \ -d '{"amount":1000,"currency":"XOF","customer_phone":"+2250700000000"}' # Simulate provider failure curl -sS -X POST "https://sandbox.api.lomi.africa/charge/wave" \ -H "X-API-KEY: $LOMI_SECRET_KEY" \ -H "X-Scenario-Key: failed" \ -H "Content-Type: application/json" \ -d '{"amount":1000,"currency":"XOF","customer_phone":"+2250700000000"}' ``` ## Automation For CI and integration tests, see [Testing guide](/build/reliability/testing) and use sandbox `curl` recipes in [Sandbox payments](/start/sandbox-payments#api-and-hosted-checkout-recipes). Integration journey Go live # Testing guide Source: https://docs.lomi.africa/build/reliability/testing This guide covers best practices for testing your lomi. integration, ensuring your payment flows work correctly in both sandbox and production environments. *** title: 'Testing guide' description: 'This guide covers best practices for testing your lomi. integration, ensuring your payment flows work correctly in both sandbox and production environments.' --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This page is the **testing strategy** guide: how to set up a sandbox app, webhooks locally, and CI. Test card numbers and MoMo behavior live on [Sandbox payments](/start/sandbox-payments). The failure-path matrix lives on [Simulate errors](/build/reliability/simulate-errors). ## Test environment setup ### 1. Test API key Always use your **Test API Key** (`lomi_sk_test_...`) for development and testing. Obtain this key from your lomi. Dashboard under **Developers -> API Keys**. Configure your application to use this key, typically via an environment variable. ```typescript filename="Configuring the SDK with test key" import { LomiSDK } from '@lomi./sdk'; // Your SDK package const lomi = new LomiSDK({ apiKey: process.env.LOMI_TEST_SECRET_KEY, // Use the TEST key from environment baseUrl: 'https://sandbox.api.lomi.africa', // Point to the sandbox URL }); ``` ### 2. Test webhook secret When testing webhooks locally or in a staging environment, create a separate webhook endpoint configuration in lomi. Dashboard pointing to your test URL (e.g., an `ngrok` URL). Use the unique **Signing Secret** (`whsec_...`) generated for *that specific test endpoint* in your test environment configuration (`LOMI_TEST_WEBHOOK_SECRET`). ### 3. Test configuration file Consider centralizing test-specific configurations. ```typescript filename="Example test configuration" // test/config.ts export const testConfig = { merchantId: process.env.TEST_MERCHANT_ID, // Your specific Test Merchant ID testWebhookUrl: process.env.TEST_WEBHOOK_URL, // Your ngrok or test server URL webhookSecret: process.env.LOMI_TEST_WEBHOOK_SECRET, defaultAmount: 100, // Smallest valid amount for testing defaultCurrency: 'XOF', }; ``` ## Payment simulation For **test card numbers**, **Wave** and **MTN** sandbox behavior, test balances, and manual QA on hosted checkout, see **[Sandbox payments](/start/sandbox-payments)**. That guide is the canonical reference for payment simulation; this page focuses on automation, webhooks, and CI. ## Integration tests Write automated tests that interact with lomi.. **sandbox** API. ### 1. Payment flow tests Test creating and retrieving core objects like Checkout sessions or Payment links. ```typescript filename="Testing checkout session creation (Jest example)" import { LomiSDK } from '@lomi./sdk'; import { testConfig } from '../config'; // Your test config describe('Checkout Session Flow', () => { let lomi: LomiSDK; beforeAll(() => { lomi = new LomiSDK({ apiKey: process.env.LOMI_TEST_SECRET_KEY!, baseUrl: 'https://sandbox.api.lomi.africa', }); }); it('should create a checkout session successfully', async () => { const sessionResponse = await lomi.checkoutSessions.createCheckoutSession({ merchant_id: testConfig.merchantId!, amount: testConfig.defaultAmount, currency_code: testConfig.defaultCurrency, success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', metadata: { test_order_id: `int_${Date.now()}` }, }); expect(sessionResponse.data.checkout_session_id).toMatch(/^cs_test_/); expect(sessionResponse.data.status).toBe('pending'); expect(sessionResponse.data.url).toContain('sandbox.checkout.lomi.africa'); }); it('should retrieve an existing checkout session', async () => { // Assume cs_test_known exists from a previous step or setup const knownSessionId = 'cs_test_xxxxxxxxxxxx'; const session = await lomi.checkoutSessions.retrieveCheckoutSession(knownSessionId); expect(session.data.checkout_session_id).toEqual(knownSessionId); // Status could be pending, completed, expired, etc. }); }); ``` ### 2. Webhook handler tests Test your webhook signature verification and event processing logic locally, without hitting the live lomi. API. ```typescript filename="Testing webhook signature verification logic" import crypto from 'crypto'; // Import your verifySignature function import { verifySignature } from '../../src/utils/security'; describe('Webhook Signature Verification', () => { const testSecret = 'whsec_test_secret_string'; const testPayload = JSON.stringify({ id: 'evt_test', event: 'PAYMENT_SUCCEEDED', data: {}, }); const payloadBuffer = Buffer.from(testPayload, 'utf8'); it('should return true for a valid signature', () => { const expectedSignature = crypto .createHmac('sha256', testSecret) .update(payloadBuffer) .digest('hex'); const isValid = verifySignature( payloadBuffer, expectedSignature, testSecret, ); expect(isValid).toBe(true); }); it('should return false for an invalid signature', () => { const invalidSignature = 'invalid_signature_string'; const isValid = verifySignature( payloadBuffer, invalidSignature, testSecret, ); expect(isValid).toBe(false); }); it('should return false if the secret is wrong', () => { const expectedSignature = crypto .createHmac('sha256', testSecret) .update(payloadBuffer) .digest('hex'); const wrongSecret = 'whsec_wrong_secret'; const isValid = verifySignature( payloadBuffer, expectedSignature, wrongSecret, ); expect(isValid).toBe(false); }); }); ``` ### 3. Error handling tests Test how your application handles specific API errors from lomi.. ```typescript filename="Testing API error handling (Jest example)" describe('API Error Handling', () => { let lomi: LomiSDK; beforeAll(() => { /* setup lomi instance */ }); it('should handle invalid request errors (400)', async () => { try { await lomi.checkoutSessions.createCheckoutSession({ merchant_id: testConfig.merchantId!, amount: -100, // Invalid amount currency_code: 'XXX', // Invalid currency success_url: 'invalid-url', // Invalid URL cancel_url: 'invalid-url', }); fail('Request should have failed'); } catch (error: any) { expect(error.statusCode).toBe(400); expect(error.message).toContain('Validation failed'); // Or specific lomi. error message // Optionally check error.details for specific field errors } }); it('should handle authentication errors (401)', async () => { const invalidLomi = new LomiSDK({ apiKey: 'lomi_sk_test_invalidkey', baseUrl: '...', }); try { await invalidLomi.providers.list(); fail('Request should have failed'); } catch (error: any) { expect(error.statusCode).toBe(401); expect(error.message).toContain('Invalid API key'); } }); }); ``` ## End-to-end (E2E) testing Simulate a full user journey involving payment. ### 1. Setup test environment Use tools like Playwright or Cypress along with a testing framework. Your setup script should: * Start your application. * Start a webhook listener (e.g., using `ngrok` and a simple server, or a dedicated test helper). * Ensure necessary test data exists in lomi. sandbox (e.g., test merchant, products). ### 2. Simulate payment flow Your E2E test script would typically: 1. Navigate through your application to initiate a payment. 2. Trigger the creation of a lomi. Checkout Session via your backend. 3. **(Challenge)** Interact with lomi.. sandbox checkout page. This is often difficult/flaky in automated E2E tests. Consider: * Using the [sandbox test card numbers and mobile money rules](/start/sandbox-payments) for manual or semi-automated flows. * Having a dedicated API endpoint in your *test environment* that simulates the webhook callback lomi. would send upon completion (bypassing the actual sandbox UI interaction). This is often more reliable for automation. 4. Wait for and verify that your application receives the expected webhook (`PAYMENT_SUCCEEDED`). 5. Verify that your application state updated correctly (e.g., order marked as paid, service provisioned). ```typescript filename="Conceptual E2E test flow (using a webhook simulator)" describe('E2E Payment Flow', () => { it('should complete payment and update order status', async () => { // 1. User initiates checkout in the app UI (simulated via test actions) const { orderId, lomiCheckoutSessionId } = await initiateCheckoutInApp(); // 2. Simulate successful payment webhook callback // (Instead of UI interaction, call your test simulation endpoint) await simulateLomiWebhook(lomiCheckoutSessionId, 'PAYMENT_SUCCEEDED'); // 3. Wait for app to process webhook await waitForOrderStatusUpdate(orderId, 'PAID'); // 4. Assert final application state const orderStatus = await getOrderStatusFromApp(orderId); expect(orderStatus).toBe('PAID'); }); }); ``` ## Test utilities ### 1. Webhook listener helper Create a helper class to capture and wait for specific webhook events during tests. ```typescript filename="Webhook event listener helper" import http from 'http'; import express from 'express'; interface RecordedEvent { id: string; type: string; receivedAt: number; payload: any; } export class TestWebhookListener { private app = express(); private server: http.Server | null = null; private receivedEvents: RecordedEvent[] = []; private port = 9090; // Or choose dynamically constructor() { this.app.use(express.raw({ type: 'application/json' })); this.app.post('/test-webhook', (req, res) => { try { // Basic validation - PRODUCTION needs full signature verification! const payload = JSON.parse(req.body.toString()); console.log(`Test listener received event: ${payload.event}`); this.receivedEvents.push({ id: payload.id, type: payload.event, receivedAt: Date.now(), payload, }); res.status(200).json({ received: true }); } catch (e) { console.error('Test listener error parsing body', e); res.status(400).json({ error: 'Invalid payload' }); } }); } start() { this.server = this.app.listen(this.port); console.log(`Test webhook listener started on port ${this.port}`); return `http://localhost:${this.port}/test-webhook`; // URL to configure in lomi. } stop() { this.server?.close(); console.log('Test webhook listener stopped.'); } async waitForEvent( eventType: string, timeoutMs = 10000, ): Promise { const start = Date.now(); while (Date.now() - start < timeoutMs) { const found = this.receivedEvents.find((e) => e.type === eventType); if (found) return found; await new Promise((r) => setTimeout(r, 200)); // Poll interval } throw new Error(`Timeout waiting for webhook event type: ${eventType}`); } } ``` ### 2. Test data generator Generate consistent test data. ```typescript filename="Test data generation utility" export class TestDataFactory { static createCheckoutData(overrides: Partial = {}) { return { merchant_id: process.env.TEST_MERCHANT_ID!, amount: 100, currency_code: 'XOF', success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', metadata: { testId: `td_${Date.now()}` }, ...overrides, }; } // Add other data generation methods... } ``` ## CI/CD integration Integrate automated tests into your CI/CD pipeline (e.g., GitHub Actions). ```yaml filename="GitHub Actions testing workflow example" # .github/workflows/test.yml name: Run Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install Dependencies run: npm ci - name: Run Unit & Integration Tests run: npm test env: LOMI_TEST_SECRET_KEY: ${{ secrets.LOMI_TEST_SECRET_KEY }} LOMI_TEST_WEBHOOK_SECRET: ${{ secrets.LOMI_TEST_WEBHOOK_SECRET }} TEST_MERCHANT_ID: ${{ secrets.TEST_MERCHANT_ID }} # Add other necessary test env vars ``` CI/CD integration Error handling API reference # How do I verify a payment? Source: https://docs.lomi.africa/build/reliability/verify-payments Confirm payment status server-side with the API and webhooks, never trust the browser redirect alone. *** title: 'How do I verify a payment?' description: 'Confirm payment status server-side with the API and webhooks, never trust the browser redirect alone.' docType: how-to --------------- import { Callout } from '@/components/docs/docs-callout'; import { DocsAgentIndex } from '@/components/docs/docs-agent-index'; A customer may close the browser, lose network connectivity, or return to your `success_url` before lomi. has finished processing, especially with **mobile money** and **3D Secure** cards. Treat the redirect as a UX hint, not proof of payment. Do not ship goods, unlock content, or mark an invoice paid based only on `success_url` query parameters or client-side JavaScript. Always verify on your server. ## Recommended verification order 1. **Webhook**: lomi. POSTs to your HTTPS endpoint when the transaction reaches a final state (preferred for async rails). 2. **API read**: `GET /transactions/{id}` or list with filters when you have the transaction ID from the checkout session or charge response. 3. **Dashboard**: manual ops and support; not for production fulfillment automation. ## Hosted checkout After the customer pays: 1. Store your internal `order_id` in checkout session `metadata` when you create the session. 2. On `success_url`, show a “processing” state, not “paid”-until your backend confirms. 3. When you receive `transaction.completed` (or your configured event type), match `metadata.order_id` and fulfill. See [Payment and payout lifecycle](/build/reliability/payment-lifecycle) for status meanings and [Handling webhooks](/build/reliability/handling-webhooks) for signature verification. ## Direct charges (Wave, MTN, card) | Rail | When to verify | | --------------------- | ----------------------------------------------------------------------- | | **Wave (live)** | After customer approves in the Wave app, webhook or poll transaction | | **MTN (live)** | Starts `PENDING`; verify before fulfill | | **MTN / Wave (test)** | May complete immediately; still verify server-side in integration tests | | **Card** | After `client_secret` confirmation; watch for `requires_action` | ## Idempotency on fulfill Your fulfillment handler (webhook worker or API poller) must be **idempotent**: processing the same `transaction_id` or webhook event `id` twice must not double-ship. Store processed event IDs or use database constraints on `order_id` + `status = fulfilled`. ## Confirm before you fulfill * `success_url` shows pending until server confirms * Webhook endpoint verifies `X-Lomi-Signature` on the raw body * Fulfillment runs only when `transaction_status` is `completed` * Refunds and chargebacks have a reversal path Integration journey List transactions Sandbox payments # Webhook reliability Source: https://docs.lomi.africa/build/reliability/webhook-reliability Retries, idempotency, delivery logs, and safe handling of duplicate events. *** title: Webhook reliability description: Retries, idempotency, delivery logs, and safe handling of duplicate events. ---------------------------------------------------------------------------------------- Production integrations must assume **at-least-once** delivery: the same logical event may arrive more than once. This guide complements [Handling webhooks](/build/reliability/handling-webhooks) with operational behavior. ## Respond quickly, process async Webhooks are delivered as plain HTTP `POST` requests. Your handler’s main job in the hot path is narrow: **authenticate the payload** (signature), **persist or enqueue the minimum you need** so you will not lose work, and **return a success status to lomi.** right away. Return **2xx** promptly after you validate the signature and persist enough to acknowledge receipt. Heavy work should run in **your** background queue. If you block on third-party APIs, large database transactions, or sequential business rules before responding, you risk exceeding lomi.’s outbound HTTP read timeout (**approximately four seconds per attempt**: see [timeouts and retries](#timeouts-retries-and-client-errors)). Keeping the HTTP response under a second in the common case leaves headroom for bursts and cold starts. ## Idempotent processing Use a stable **event envelope id** or a tuple of **(event type, canonical resource id)** to detect duplicates: * If you have already applied the event, **skip** or **no-op** safely. * Never assume “exactly once” without storing processed ids. This matches server-side patterns where transaction updates **merge metadata** and balance updates check **already processed** flags. ## Retries and delivery logs Operational debugging almost always starts with what lomi. saw on the wire. Your endpoint’s response code, body snippet, and timings are written to **webhook delivery logs**, which you can read from the Dashboard or the [Webhooks](/api/webhooks) API. Use them to: * confirm whether your endpoint returned non-2xx * inspect latency and payload size * debug signature or parsing issues ### Fetch delivery logs ```bash curl -X GET "https://api.lomi.africa/webhooks/YOUR_WEBHOOK_ID/logs?limit=10&failed=true" \ -H "X-API-Key: YOUR_API_KEY" ``` `GET /webhooks/{id}/logs` accepts `limit` (max 100), `offset`, `success`, and `failed`. ### Retry a failed delivery ```bash curl -X POST "https://api.lomi.africa/webhooks/YOUR_WEBHOOK_ID/logs/YOUR_LOG_ID/retry" \ -H "X-API-Key: YOUR_API_KEY" ``` Manual retry is for a specific failed log entry. Automatic retries stay bounded: `4xx` is usually terminal for that sequence, while `5xx` and timeouts may retry. See [timeouts and retries](#timeouts-retries-and-client-errors).

Timeouts, retries, and client errors

The sections below describe how lomi. delivers webhooks today so you can plan for **timeouts**, **retries**, and **non-retryable client errors**. Product behavior can evolve; when in doubt, rely on delivery logs and **idempotent** handlers. ### Per-attempt HTTP timeout lomi.’s webhook HTTP client waits **approximately 4 seconds** (`timeout: 4000` ms per request) for your server to finish the HTTP response. * Aim to send **`200` / `204` immediately** after verifying `X-Lomi-Signature` (well under \~1 s), then enqueue business logic elsewhere. * If you exceed \~4 s, from lomi.’s viewpoint the delivery **failed due to timeout** for that attempt (subject to retries, see below). ### When lomi retries (and when it does not) On **each failed attempt**, lomi. classifies the HTTP response as follows: | Outcome | Retries? | | ----------------------------------------------------------------- | ------------------------------------------------------------------------ | | **2xx** | No, delivered | | **4xx client errors** (`400`–`499`, incl. **`401 Unauthorized`**) | **No further attempts** for that delivery (“non-retryable client error”) | | **5xx** and transient/network errors | **Yes**: subject to backoff and attempt caps | In practice: **fix configuration and auth issues** (which often surface as `4xx`) before expecting automatic healing, lomi. will not keep retrying a misconfigured URL. ### Retry attempt limits For **retryable** failures (timeouts, `5xx`, transient network faults), lomi. may attempt delivery up to **four** times on some paths, or up to **five** times when delivery is queued, with **exponential backoff** (initial delay about **3–5 seconds** between attempts). You do not need to distinguish delivery paths in your handler, **always** process events idempotently, but attempt counts help when you read delivery logs after an outage. ### Payload envelope (`lomi_environment`) The JSON field **`lomi_environment`** reflects the deployment environment (`production`, `development`, etc.). It is **not** a substitute for distinguishing **live** vs **test** traffic, use your API key environment and webhook endpoint configuration for that. ## Signature verification Verify using the **raw request body** bytes. Re-serialized JSON can break HMAC or similar schemes. Details: [Handling webhooks](/build/reliability/handling-webhooks) and [API integration](/start/first-payment). ## Test vs live Webhook endpoints and secrets are **environment-specific**. Promote configurations carefully so test URLs and secrets never receive live traffic. ## Related API reference * [Webhooks API](/build/reliability) * [Payment and payout lifecycle](/build/reliability/payment-lifecycle) # Acceptable use Source: https://docs.lomi.africa/start/merchant-of-record/acceptable-use What you can and cannot sell through lomi. Two tiers: prohibited businesses, and businesses that need pre-approval and a licence. *** title: 'Acceptable use' description: 'What you can and cannot sell through lomi. Two tiers: prohibited businesses, and businesses that need pre-approval and a licence.' ------------------------------------------------------------------------------------------------------------------------------------------------ This Acceptable use policy is part of the [Terms and Conditions](https://lomi.africa/terms). Using lomi. means you accept both. Last updated: September 8, 2026. lomi. is a payment processor. You remain the seller of your goods and services; we move the money through licensed partner banks, e-money issuers, card acquirers and mobile-money operators. Those partners, the card networks (Visa, Mastercard, GIM-UEMOA) and the mobile-money operators each publish their own list of prohibited merchant categories. **Those lists are incorporated into this policy** and apply to you in full, in addition to the items below. ## Tier 1: prohibited We never process payments for these activities, whatever the country or licence. *This is not an exhaustive list.* * Money transfer, remittance, hawala, cash-out, or any activity whose real purpose is to move funds rather than to sell your own goods or services * Collecting payments for someone else, acting as a payment intermediary or aggregator, or sending payouts to a wallet or bank account that is not your verified business * Payment links, invoices, or checkout pages that do not name a real good or service you sell, including generic "prestation" / "service payment" links or test links used to take live payments * Card testing, mule accounts, or high-velocity attempts that do not match a genuine customer purchase * Illegal drugs, drug paraphernalia, and products that mimic controlled substances * Weapons, ammunition, explosives, and related components * Counterfeit goods, replicas, and products you do not own the IP for or hold the licences to resell * Adult services or content, including AI-generated (for example AI girlfriend / boyfriend services), escort services and dating sites with adult content * Pyramid schemes, multi-level marketing, get-rich-quick programs, and any scheme where returns depend on recruiting others * Unlicensed financial services: lending, deposit-taking, investment advice, forex, binary options, crypto-asset exchange or custody, and buying or selling crypto-assets for fiat * IPTV and unlicensed streaming, virus, spyware, hacking tools, and services that sell access to other people's data * Deceptive products: fake testimonials, fake reviews, or branding designed to make consumers believe the product is another company's * Hate, violence, terrorism, or harassment content or services * Any activity involving a person or country subject to UN, EU, OFAC, UK, UEMOA or Ivorian sanctions * Any activity prohibited by our payment partners, the card networks, or the mobile-money operators we route through ## Tier 2: pre-approval and licence required These activities are legal in some countries but carry higher regulatory or dispute risk. You must ask us before you sell them, show the licence or registration that applies to you, and may be placed on a rolling reserve or a specific pricing schedule. We may decline even when you hold a licence if a payment partner will not support the category. * Gambling, betting, lotteries, and fantasy sports (licensed operators only) * Alcohol, tobacco, e-cigarettes and vaping products (age-restricted retail with a licence) * Pharmacies, telemedicine, and nutritional supplements with health claims * Travel agencies, airlines, event ticketing, and other services delivered far in the future * Real-estate deposits, school fees collected by an intermediary, and other third-party collection with a written mandate * Regulated professions (legal, notarial, insurance brokerage, microfinance) that need a professional card or agrément * Marketplaces and platforms that sell on behalf of other sellers * Subscription businesses with free trials that convert to paid, and negative-option billing * Advertising, lead generation, and affiliate networks * Charities and fundraising (registration required) * Precious metals and stones, and high-value collectibles ## Enforcement We reserve the right to add to these lists at any time, place your account under review, pause live collection and payouts, hold funds through the dispute window, refuse specific transactions, or suspend the account without notice if we consider the usage deceptive, fraudulent, or high-risk. The [Terms and Conditions](https://lomi.africa/terms) describe how holds, reserves, suspension and termination work and how to contest a decision. For questions about acceptable use or to verify whether your business qualifies, contact our [compliance team](mailto:hello@lomi.africa) before you start selling. # Account reviews Source: https://docs.lomi.africa/start/merchant-of-record/account-reviews How lomi. verifies your account, monitors activity, and keeps payouts and payments secure. *** title: 'Account reviews' description: 'How lomi. verifies your account, monitors activity, and keeps payouts and payments secure.' --------------------------------------------------------------------------------------------------------- lomi. is a **payment processor**: you remain the seller of your goods and services, and we process the payments through licensed partner banks, e-money issuers, card acquirers and mobile-money operators. Those partners, the card networks and the regulators (BCEAO, Ivorian law) require us to know who we process for. To protect you, your customers, and the payment network, every account goes through verification and ongoing review aligned with our [acceptable use](/start/merchant-of-record/acceptable-use) policy. ## Activation process Before you accept live payments, you complete **KYC/KYB** (know your customer / business). lomi. typically finishes initial review within 12 hours, often faster. During activation, you provide: * A short survey about your business, products, and how you plan to use lomi. * Identity verification for the owners and signatories: passport or national ID, business registration (for example RCCM), and tax ID (for example NINEA or DFE). * Beneficial owners holding 25% or more, and the bank account or mobile-money wallet in the business's name for payouts. This review runs right after sign-up so your account meets **KYC/AML** requirements for payment platforms in West Africa, including BCEAO regulations and Côte d'Ivoire law. Part of the check is automated (document authenticity, sanctions and PEP screening, registry lookups). Any decision that refuses or restricts your account is reviewed by a person, and you can contest it by replying to the review email. ### Submit documents early Provide KYC/KYB documents as soon as you sign up. Early submission speeds activation and helps you use payouts, checkout, and subscriptions without delays later. If we ask for something during a review, answer within 10 business days; incomplete files are the most common reason payouts stay paused. ## Continuous reviews (async) lomi. monitors account activity to prevent fraud. At certain sales thresholds, lomi. may run additional reviews asynchronously, often within hours and without asking for more documents. Reviews consider: * Transaction history and risk signals * Refund and chargeback rates * Sales volume and average ticket relative to your account profile * Consistency between your stated business, your payment links, and your customers' geography ### Holds and reserves: what you can expect You receive an email when a review is in progress. **Payouts may pause** during a review. When risk requires it, **live collection may pause as well** (checkout and Payment Links can be deactivated). lomi. may ask for greffe or registry confirmation (for example RCCM or NINEA) and a bank or acquiring-partner confirmation before releasing funds. When we place a hold or a reserve, the email and the dashboard tell you: * the general reason for the review, * the amount or percentage concerned, * the documents or actions that would resolve it, * the expected review or release date. A hold lasts until the review is complete, and at most through the card-scheme dispute window (today up to 120 days). A rolling reserve on a high-risk profile does not exceed 10% of processed volume held for 180 days unless a payment partner requires more. You can contest a hold or reserve by writing to [hello@lomi.africa](mailto:hello@lomi.africa); we acknowledge within 2 business days and answer within 7 business days, the complaint-handling deadline set by BCEAO Instruction No. 001-01-2024. If you are not satisfied with our answer, you can refer the matter to the Observatoire de la Qualité des Services Financiers of Côte d'Ivoire or to the Commission Bancaire de l'UMOA. The full rules are in the [Terms and Conditions](https://lomi.africa/terms). ### High chargeback ratios Card networks treat chargebacks above \~0.7% of sales as excessive. Breaching that level can trigger costly monitoring programs, penalties, or account termination. Mobile money reversals are typically near zero. If your chargeback rate rises, lomi. may contact you to lower it before thresholds are reached, ask for a remediation plan, or apply a reserve. Dispute fees and the evidence window are described on [Pricing](/start/merchant-of-record/pricing) and in the Terms. ## Supported countries ### Payments You can accept local payments across West Africa and international card payments without hidden fees. Some mobile-money routes may be restricted by operator; when that happens, your customers see clear errors at checkout and you receive email notification. Because lomi. is a processor and not the seller, **you remain responsible for the taxes on your sales** (VAT, sales tax, withholding) and for the licences your business needs. We add applicable taxes on our own fees only. ### Payouts lomi. supports low-cost Mobile Money and free bank payouts to businesses established in the UEMOA (West African Economic and Monetary Union). Payouts go only to an account or wallet in the verified business's name. **Local payout countries** today include: * Sénégal * Côte d'Ivoire * Mali * Burkina Faso * Guinea-Bissau * Niger * Ghana * Rwanda * More countries are being added regularly. For countries not listed above, lomi. also offers international bank payouts with a fee. See [Pricing](/start/merchant-of-record/pricing) for details. # Pricing Source: https://docs.lomi.africa/start/merchant-of-record/pricing Transparent fees at a 20% discount vs. competition. *** title: 'Pricing' description: 'Transparent fees at a 20% discount vs. competition.' ------------------------------------------------------------------ Standard (fixed) pricing uses a stable fee schedule. Volume-tiered pricing starts on Starter and decreases as monthly processed volume grows. Exact rates for your organization are in **Billing → Pricing** in the dashboard and may differ by payment method and provider. * **Dynamic (volume-tiered):** you start on the **Starter** tier, then rates decrease automatically as your monthly processed volume grows into Growth, Professional, and Enterprise. * **Fixed (standard):** fees stay the same regardless of monthly volume, best when you want predictable costs. * **Custom:** negotiated pricing for specific business models, risk profiles, or high-volume needs. Starter dynamic rates are typically configured at or above standard fixed rates so merchants who expect to grow benefit from lower tiers over time. Compare both plans in **Billing → Pricing** before switching. You can switch between standard and volume-based pricing **at most once per calendar month** from the dashboard. Organizations on **custom** negotiated fees cannot self-serve a switch to volume-based pricing. **Fee changes.** We give at least **two months' notice** by email and in the dashboard before a fee change takes effect, as required by Article 57 of BCEAO Instruction No. 001-01-2024 on payment services. Reductions and promotions can apply on publication; a change required by law, a regulator, a card network, or a payment partner within a shorter period takes effect on the date so required, and we tell you as early as possible. If you do not accept a change, you can close your account free of charge before it takes effect; if you have not told us you disagree by the effective date, the change is deemed accepted. Processing fees are not returned on refunds, and taxes are added to our fees where applicable. The binding terms are in the [Terms and Conditions](https://lomi.africa/terms). ## How dynamic pricing works With dynamic pricing, your organization is assigned to a tier based on **last closed month** processed volume in **F CFA** (all currencies converted, not same-day sales). As your volume increases, your transaction rates decrease automatically. * Tier assignment uses **total processed volume converted to F CFA**: sales in XOF, USD, and EUR all count toward your monthly tier. * Tier rates apply across payment processing (Mobile Money, cards, POS) and selected payout/other fees. * Non-tier fees can still apply (for example refunds, disputes, international card surcharges, or subscription-related add-ons). ## Volume tiers (F CFA monthly revenue) ## Dynamic tier fees Rates below apply when your organization is on the **volume-tiered** plan, based on your current tier. For current tier thresholds and live rates, use the pricing view in your dashboard or the public pricing page at [lomi.africa/pricing](https://lomi.africa/pricing). ## Fixed default fees These rates apply on the **fixed** plan. ### F CFA ### USD and EUR Provider-specific Mobile Money rates (MTN, Wave, Moov, Airtel) follow the same percentage and fixed structure for your active plan and tier. ### Worked examples You can add discounts or additional fees (like delivery, taxes, or even transaction fees) directly from the merchant dashboard to customize pricing or absorb processing costs. ## Fixed and custom pricing Not every business should be on volume-tiered pricing: * **Fixed** is useful when you want predictable fees for finance planning. * **Custom** is useful when your payment mix, ticket size, geography, or compliance setup needs a tailored schedule. Custom pricing can include bespoke combinations of percentage and fixed components, and can vary by payment method and currency. ## Refunds and disputes lomi. reserves the right to issue refunds at our discretion within 60 days of a purchase. This helps proactively reduce disputes and chargebacks. We only exercise this right to help minimize chargebacks and reduce associated fees on your behalf. Transaction fees are calculated from: `transaction fee = (percentage × amount) + fixed amount` Depending on your pricing mode, currency, and method, the fee can include base processing, provider-specific adjustments, and optional add-ons. **Important:** * Payment providers impose monitoring programs, penalties, and higher chargeback costs for sellers with high chargeback rates. * lomi. monitors chargeback rates on your account to stay within network thresholds. * If chargebacks rise significantly, your account may be reviewed or payouts paused until the issue is resolved. In some low-amount flows, only the percentage component may apply based on your organization fee configuration. ## Payout fees lomi. does not add markup on merchant withdrawals where the platform fee is zero. Third-party operator fees may still apply on certain payout routes. For the up-to-date fee values (F CFA/USD/EUR and any add-ons), rely on your live pricing configuration in the dashboard and on [lomi.africa/pricing](https://lomi.africa/pricing). > Contact our [sales team](mailto:hello@lomi.africa) to discuss **custom options** tailored to your business needs. # Refunds Source: https://docs.lomi.africa/start/merchant-of-record/refunds How refunds work for payments processed through lomi. *** title: 'Refunds' description: 'How refunds work for payments processed through lomi.' -------------------------------------------------------------------- Refunds for payments processed through lomi. are initiated by the merchant (dashboard, API, MCP, or CLI) or, in limited cases, by lomi. to reduce disputes. ## Merchant-initiated refunds * Full and partial refunds are available on completed transactions, subject to provider rules and available balance. * Use [Refunds](/build/money/refunds) for the product guide and [Create refund](/api/refunds/RefundsController_create) for the API. * **Processing fees are not returned** when you refund a payment. Full refunds are free; partial refunds carry the refund fee listed on [Pricing](/start/merchant-of-record/pricing). Refunds are debited from your balance; if the balance is insufficient, they are recovered from later payments or invoiced. * Card refunds go back to the original card. Mobile Money and instant-payment refunds go back to the original wallet or account where the operator allows it. ## Platform-initiated refunds lomi. may issue a refund within 60 days of a purchase when that is the practical way to avoid a chargeback, when the customer shows the payment was unauthorized or the goods were not delivered, or when a payment partner or authority instructs it. We do this to protect the merchant from network fees and dispute liability, not as a substitute for your own customer policy. You are notified of each platform-initiated refund. ## Your customer policy Publish a clear refund policy on your checkout and website. lomi. does not replace that policy for goods and services you sell. Fee schedule: [Pricing](/start/merchant-of-record/pricing). Binding legal terms: [lomi.africa/terms](https://lomi.africa/terms). # API reference authoring Source: https://docs.lomi.africa/resources/contributing/api-reference-authoring How we maintain the hand-written API section, keep it aligned with OpenAPI, and avoid accidental destructive generation. *** title: API reference authoring description: How we maintain the hand-written API section, keep it aligned with OpenAPI, and avoid accidental destructive generation. ------------------------------------------------------------------------------------------------------------------------------------- The public REST reference lives under `content/docs/api/` in the docs app. **Narrative copy and examples are authored manually in MDX** with a fixed section layout, not `` autogenerated wrappers. The committed `apps/docs/openapi.json` is the machine-readable **contract and validation artifact**, not the prose source of truth. ## Safe workflow (default) 1. **Normal docs build** (`pnpm run build`) runs `build:pre`, which **does not** re-export OpenAPI from `apps/api` or rewrite `openapi.json`. It uses whatever is already committed (and builds the search registry). 2. When the API contract changes, update the artifact **deliberately**: * From `apps/api`: `pnpm run openapi:export` (writes `apps/docs/openapi.json`). CI must stay green: the exported file must match the repo (`verify-openapi` in GitHub Actions). * Optional: from `apps/docs`, sync export + security normalization in one step: `pnpm run build:pre:sync` (sets `DOCS_SYNC_OPENAPI=1`). 3. **Do not** run mass REST regeneration unless you actually need scaffolding updates. Safe mode only creates missing operation pages: ```bash CONFIRM_BOOTSTRAP=1 pnpm run api:regenerate-rest-reference ``` Alias: `pnpm run api:bootstrap` (same script; still requires `CONFIRM_BOOTSTRAP=1`). To overwrite existing operation pages too, opt in explicitly: ```bash CONFIRM_BOOTSTRAP=1 BOOTSTRAP_OVERWRITE=1 pnpm run api:regenerate-rest-reference ``` Then **edit** pages for clarity: examples, edge cases, links to guides, and wording. 4. Run `pnpm lint` in `apps/docs`, it checks OpenAPI tone rules, **REST parity** (every public merchant operation has a doc), required **headings**, internal links, and **policy** (no provider-ingress paths or vendor-internal identifiers in public docs/OpenAPI). ## Policy: provider internals * **Inbound provider webhooks** (card processor / mobile-money ingress) are **not** part of the public merchant API. They must not appear in `openapi.json`, REST MDX, or high-level guides. Lint enforces this. * Describe **merchant-facing outbound webhooks** and **your** integration surface only. ## Frontmatter (required for each operation page) Each file must include: | Field | Example | Notes | | ------------- | ------------------------------- | ----------------------------------- | | `title` | Short human title | Mirrors OpenAPI `summary` initially | | `description` | One line | Search / meta | | `method` | `get` | Lowercase HTTP verb | | `path` | `/accounts/balance` | Must match OpenAPI path key exactly | | `operationId` | `AccountsController_getBalance` | Matches Nest `operationId` | | `full` | `true` | Keeps wide API layout | ## Required sections (headings) Lint enforces these headings: `## Overview`, `## Authentication`, `## Endpoint`, `## Request`, `## Responses`, `## Errors`, `## Example`, `## OpenAPI` (French pages may use the localized heading set; see lint rules.) ## What we exclude * **Internal and admin-shaped routes** are not listed in the public REST reference. The public route filter excludes agent routes, webhook provider ingress, metering/invoices, and account list/detail (`GET /accounts`, `GET /accounts/{id}`). **Read-only platform** routes (`organizations`, `merchants`, `providers`) are included in the public contract and SDK allowlist. ## Legacy URLs Older bookmarks used flat paths like `/api/AccountsController_getBalance`. The app **redirects** those to the canonical grouped path `/api/balances/AccountsController_getBalance`. ## Scripts reference | Script | Role | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `lib/scripts/pre-build.ts` | Registry build; **optional** OpenAPI export when `DOCS_SYNC_OPENAPI=1` (`build:pre:sync`). Does **not** overwrite REST MDX. | | `lib/scripts/bootstrap-manual-api-reference.ts` | Regenerates operation scaffolding + `meta.json` from `openapi.json`; default is **missing-only** (`CONFIRM_BOOTSTRAP=1`), full overwrite requires `BOOTSTRAP_OVERWRITE=1`. | | `lib/scripts/manual-api/render-operation-mdx.ts` | Template for one operation page | | `lib/scripts/manual-api/_expected-public-operations.json` | Generated list of `METHOD /path` strings (bootstrap output) | # Best practices Source: https://docs.lomi.africa/resources/contributing/best-practices Following these best practices helps maintain code quality, consistency, and security across lomi. project. *** title: 'Best practices' description: 'Following these best practices helps maintain code quality, consistency, and security across lomi. project.' -------------------------------------------------------------------------------------------------------------------------- ## Code style Maintain clean, readable code. We use Prettier and ESLint for automatic formatting and linting (`bun run lint:fix`). ### Formatting and Readability ```typescript filename="Code Style Example" // Good: Readable, consistent formatting, meaningful names function isValidAmount(amount: number, maxAmount: number = 1000000): boolean { return amount > 0 && amount <= maxAmount; } // Bad: Poor formatting, short unclear names, magic numbers # function validate(a){return a>0&&a<=1000000} ``` ## Code standards Maintain high code quality by following these standards: ### TypeScript Use TypeScript for type safety and clarity. ```typescript filename="TypeScript Example" // Good: Clear interface and types interface PaymentRequest { amount: number; // Amount in smallest currency unit (e.g., cents) currency: string; // ISO 4217 currency code (e.g., 'XOF') providerCode: string; // Unique identifier for the payment provider } async function processPayment(request: PaymentRequest): Promise { // Implementation... // Use types for return values as well } ``` ## Security Security is paramount. Follow these guidelines to protect user data and system integrity. ### Environment variables Never commit sensitive keys or secrets directly into the codebase. Use environment variables. ```bash filename=".env.example" # .env.example - Provide a template for required variables LOMI_SECRET_KEY= LOMI_WEBHOOK_SECRET= NODE_ENV=development ``` ```bash filename=".gitignore" # .gitignore - Ensure local environment files are not tracked .env .env.local *.log ``` Refer to the [API Environment documentation](/start/api-keys) for managing API keys. ### Sensitive data handling Avoid logging sensitive information like API keys, passwords, or personal user data. If necessary, ensure it's properly redacted. ```typescript filename="Redaction Example" // Example: Redact sensitive information in logs const sanitizeData = (data: any) => { const masked = { ...data }; // Define sensitive keys specific to your context const sensitiveKeys = [ 'apiKey', 'secretKey', 'password', 'authorization', 'phoneNumber', ]; sensitiveKeys.forEach((key) => { if (masked[key]) masked[key] = '[REDACTED]'; }); return masked; }; ``` Always validate and sanitize user input to prevent injection attacks. Consider practices like [Webhook Signature Verification](/build/reliability/handling-webhooks) to ensure data integrity. ## Error Handling Handle errors gracefully and provide context. ### Graceful Handling ```typescript filename="Error Handling Example" // Good: Catch specific errors, provide context, log appropriately import { logger } from './logger'; // Assuming a logger utility try { const result = await processPayment(request); logger.info({ transactionId: result.id }, 'Payment successful'); } catch (error) { if (error instanceof ValidationError) { logger.warn({ error, request }, 'Invalid payment request'); // Return specific error response to client } else if (error instanceof PaymentProviderError) { logger.error({ error, request }, 'Payment provider failed'); // Handle provider-specific failure (e.g., retry logic, alert) } else { logger.error({ error, request }, 'Unexpected error during payment processing'); // Handle unknown error } } # Bad: Ignores error, logs vaguely /* try { await processPayment(request); } catch (error) { console.error("Something went wrong"); // Lacks context } */ ``` ## Testing Write meaningful tests using Vitest (`describe`, `it`, `expect`). Aim for comprehensive unit, integration, and end-to-end tests. ```typescript filename="Testing Example (Vitest)" import { describe, it, expect } from 'vitest'; import { processPayment } from '../src/payment-processor'; // Adjust path describe('Payment Processing', () => { it('should process a valid payment request', async () => { const request: PaymentRequest = { amount: 10000, // e.g., 100.00 XOF currency: 'XOF', providerCode: 'PROVIDER_X', }; // Mock dependencies if necessary // vi.mock(...) const result = await processPayment(request); expect(result).toBeDefined(); expect(result.status).toBe('completed'); // Add more specific assertions }); it('should throw validation error for invalid amount', async () => { const request: PaymentRequest = { amount: -500, currency: 'XOF', providerCode: 'PROVIDER_X', }; await expect(processPayment(request)).rejects.toThrow(ValidationError); }); }); ``` ## Git workflow Adhere to our established Git workflow for smooth collaboration. ### Branch management Keep feature branches focused on a single task and relatively short-lived. Regularly rebase with the `develop` branch. ```bash filename="Terminal" # Create a focused feature branch git checkout develop git pull upstream develop git checkout -b feature/add-wave-provider # Work on the feature... # Commit changes following guidelines git commit -m "feat(payments): implement Wave provider" # Push the branch git push origin feature/add-wave-provider ``` See the full [Branching Strategy](/resources/contributing/branching-strategy) for details. ### Commit messages Use conventional commit messages to provide clarity and enable automated changelog generation. ```bash filename="Terminal - Good Commits" # Good: Specific type, scope, and description git commit -m "feat(auth): implement API key generation endpoint" git commit -m "fix(webhooks): handle duplicate event processing" git commit -m "docs(contributing): clarify rebase workflow" # Bad: Vague, uninformative # git commit -m "fixed stuff" # git commit -m "wip" # git commit -m "more changes" ``` ## Documentation Good documentation is essential for maintainability and collaboration. ### Code comments Use comments to explain *why* something is done, not *what* it does (the code should explain the what). Use TSDoc for functions and complex logic. ````typescript filename="TSDoc Example" /** * Processes a payment request using the specified provider. * Handles potential errors and ensures idempotency. * @param request - The payment request details. * @returns A promise resolving to the payment result. * @throws {ValidationError} If the request payload is invalid. * @throws {PaymentProcessingError} If the provider fails. * @example * ```typescript * const result = await processPayment({ amount: 5000, currency: 'XOF', providerCode: 'WAVE' }); * console.log(result.transactionId); * ``` */ async function processPayment(request: PaymentRequest): Promise { // Implementation details... } ```` ### README files Ensure packages and significant components have `README` files explaining their purpose, usage, configuration, and how to run tests. ```markdown filename="README Example" # Package/Component Name ## Overview A brief description of what this package/component does. ## Installation `pnpm add @lomi/package-name` ## Usage Provide clear code examples and instructions. ## Configuration Detail any available configuration options. ## Testing Explain how to run tests for this specific package/component. `pnpm --filter @lomi/package-name test` ``` ## Deployment Follow standard procedures for deployment. ### CI/CD Continuous Integration and Continuous Deployment pipelines automate testing and deployment. Ensure your changes pass all checks. ```yaml filename=".github/workflows/ci.yml Example" # Example: .github/workflows/ci.yml (Simplified) name: CI Checks on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2 with: version: 8 # Specify pnpm version - uses: actions/setup-node@v3 with: node-version: '18' # Specify Node.js version cache: 'pnpm' - run: pnpm install - run: pnpm lint - run: pnpm test ``` ### Version control Package versions are managed within their respective `package.json` files and updated according to our [Versioning](/resources/contributing/versioning) guide. ```json filename="package.json Example" // Example: packages/api/package.json { "name": "@lomi/api", "version": "1.2.3", "engines": { "node": ">=18" }, "main": "dist/index.js" } ``` Code reviews Branching strategy Getting support # Branching strategy Source: https://docs.lomi.africa/resources/contributing/branching-strategy We follow a Git workflow inspired by GitFlow but simplified for continuous delivery and integration. *** title: 'Branching strategy' description: 'We follow a Git workflow inspired by GitFlow but simplified for continuous delivery and integration.' ------------------------------------------------------------------------------------------------------------------- ## Main branches Our strategy is to ensure a stable production branch (`main`) while facilitating active development on the `develop` branch. These are the long-lived core branches of the repository. ### `main` branch * Represents the **production-ready** code. Only fully tested and approved code resides here. * **Protected**: Direct pushes are disabled. Changes must come through reviewed Pull Requests from `release` or `hotfix` branches. * **Tagged**: Each merge into `main` corresponds to a release and should be tagged with a semantic version number (e.g., `v1.2.3`). * **Deployments**: Merges to `main` trigger automated deployments to the production environment. ### `develop` branch * Serves as the primary **integration branch** for ongoing development. * Contains the latest successfully built development code, including completed features and bug fixes awaiting release. * **Protected**: Requires status checks (CI tests) to pass before merging. * **Deployments**: Changes merged into `develop` are typically deployed automatically to a staging or pre-production environment for further testing. * **Source for Features**: All feature branches should be created from `develop`. ## Feature development Short-lived branches used for specific tasks. ### Feature branches (`feature/*`) Used for developing new features. ```bash filename="Terminal - Feature Branch Workflow" # 1. Ensure your local develop is up-to-date git checkout develop git pull upstream develop # 2. Create your feature branch from develop git checkout -b feature/payment-method-wave # 3. Work on your feature, commit changes regularly # ... make changes ... git add . git commit -m "feat(payments): implement initial Wave structure" # 4. Keep your branch updated with develop (optional but recommended) git fetch upstream git rebase upstream/develop # 5. Push your feature branch to your fork git push origin feature/payment-method-wave ``` ### Bug fix branches (`fix/*`) Used for fixing non-critical bugs discovered during development. ```bash filename="Terminal - Bug Fix Branch Workflow" # 1. Create bug fix branch from develop git checkout develop git pull upstream develop git checkout -b fix/transaction-timeout-handling # 2. Fix the bug and commit # ... make changes ... git add . git commit -m "fix(transactions): increase timeout and add retry logic" # 3. Push the branch git push origin fix/transaction-timeout-handling ``` ## Release process Managed branches for preparing and executing releases. ### Release branches (`release/*`) Used to prepare a new production release. Allows for final testing, documentation updates, and minor bug fixes specific to the release. ```bash filename="Terminal - Release Branch Workflow" # 1. Create release branch from develop git checkout develop git pull upstream develop git checkout -b release/v1.2.0 # 2. Perform release tasks (e.g., bump version, update changelog) npm version minor -m "chore(release): prepare release %s" # ... final tests, documentation updates ... # 3. Push the release branch (allows CI to run tests) git push origin release/v1.2.0 # 4. Once ready, merge into main and develop, then tag main # (See Merge Strategy below) ``` ### Hotfix branches (`hotfix/*`) Used for addressing critical bugs found in the production (`main`) branch. These require immediate attention. ```bash filename="Terminal - Hotfix Branch Workflow" # 1. Create hotfix branch directly from main git checkout main git pull upstream main git checkout -b hotfix/critical-auth-issue-1.2.1 # 2. Fix the critical bug # ... make changes ... git add . git commit -m "fix(auth): resolve critical login vulnerability" # 3. Bump the patch version npm version patch -m "chore(release): hotfix %s" # 4. Push the hotfix branch git push origin hotfix/critical-auth-issue-1.2.1 # 5. Once fixed and tested, merge into main and develop, then tag main # (See Merge Strategy below) ``` ## Branch protection rules Configured in the GitHub repository settings to enforce the workflow. 1. **`main` Branch** * Require Pull Request reviews before merging (at least 1 approval). * Require status checks (CI tests, linting) to pass before merging. * Require branches to be up to date before merging. * **Disallow direct pushes.** * Enforce linear history (prefer squash or rebase merging for PRs). 2. **`develop` Branch** * Require status checks to pass before merging. * Allow maintainers to merge without review (optional, based on team policy). * Prefer squash or rebase merging for feature PRs to keep history clean. ## Merge strategy How branches are merged back into the main lines. 1. **Feature/Fix branches to `develop`** * Create a Pull Request from your `feature/*` or `fix/*` branch targeting the `develop` branch. * Ensure CI checks pass and code review is complete (if required). * **Use Squash and Merge or Rebase and Merge** via the GitHub PR interface to maintain a clean `develop` history. * Delete the feature/fix branch after merging. 2. **`release/*` Branch to `main` and `develop`** * Create a Pull Request from the `release/*` branch targeting `main`. * Ensure all final checks and approvals are met. * **Use Merge Commit (`--no-ff`)** to preserve the history of the release preparation. * After merging to `main`, **tag the merge commit** on `main` with the version number (e.g., `git tag v1.2.0 `). Push the tag (`git push upstream --tags`). * Create another Pull Request (or merge directly if permissions allow) from the `release/*` branch back into `develop` to incorporate any release-specific fixes made on the release branch. * Delete the release branch after merging into both `main` and `develop`. 3. **`hotfix/*` Branch to `main` and `develop`** * Similar to releases: Create a PR targeting `main`, merge using Merge Commit (`--no-ff`), tag the merge commit on `main`, push the tag. * Create another PR (or merge directly) from the `hotfix/*` branch back into `develop` to ensure the fix is included in ongoing development. * Delete the hotfix branch after merging into both `main` and `develop`. ## Best practices summary 1. **Branch naming conventions** * `feature/` * `fix/` * `release/v` * `hotfix/` * Use kebab-case (hyphen-separated) descriptions. 2. **Commit messages** * Follow the [Conventional Commits specification](https://www.conventionalcommits.org/). ```bash filename="Commit Message Format Example" # Format: (): # Example: feat(payments): add Wave payment provider integration ``` * Reference related issue numbers in the commit body or footer (e.g., `Closes #123`). 3. **Pull requests** * Write clear, descriptive PR titles and descriptions. * Link to the relevant issue(s). * Keep PRs focused on a single logical change. * Request reviews from relevant team members or code owners. Code reviews Versioning Best practices # Code of conduct Source: https://docs.lomi.africa/resources/contributing/code-of-conduct We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. *** title: 'Code of conduct' description: 'We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.' -------------------------------------------------------------------------------------------------------------------------------------- ## Our pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. ## Our standards Examples of behavior that contributes to a positive environment for our community include: * Using welcoming and inclusive language. * Being respectful of differing viewpoints and experiences. * Gracefully accepting constructive criticism. * Focusing on what is best for the community. * Showing empathy towards other community members. Examples of unacceptable behavior include: * The use of sexualized language or imagery, and unwelcome sexual attention or advances. * Trolling, insulting or derogatory comments, and personal or political attacks. * Public or private harassment. * Publishing others' private information, such as a physical or email address, without their explicit permission. * Other conduct which could reasonably be considered inappropriate in a professional setting. ## Enforcement responsibilities Community leaders (e.g., project maintainers, moderators) are responsible for clarifying and enforcing our standards of acceptable behavior. They will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces (e.g., GitHub repositories, Slack/Discord, mailing lists) and also applies when an individual is officially representing the community in public spaces (e.g., using an official email address, posting via an official social media account, acting as an appointed representative at an event). ## Enforcement process Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement. All complaints will be reviewed and investigated promptly and fairly. Community leaders are obligated to respect the privacy and security of the reporter of any incident. ### Enforcement actions Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 1. **Correction** * **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. * **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ```bash filename="Example Correction Message" # Example private message format "Your recent comment [link/context] was considered inappropriate under our Code of Conduct because [reason]. Please review the CoC and adjust your communication accordingly." ``` 2. **Warning** * **Community Impact**: A violation through a single incident or series of actions. * **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ```bash filename="Example Warning Message" # Example warning format "This is a formal warning regarding your behavior [link/context]. Further violations of the Code of Conduct may result in temporary or permanent restrictions from the community." ``` 3. **Permanent ban** * **Community impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. * **Consequence**: A permanent ban from any sort of public interaction within the community. ```bash filename="Example Ban Notification" # Example ban notification "Due to repeated or severe violations of our Code of Conduct, your access to the lomi. community spaces has been permanently revoked." ``` ## Reporting guidelines If you are subject to or witness unacceptable behavior, or have any other concerns, please notify the community leaders as soon as possible. ### Contact information Reports can be made via: * **Email**: [`conduct@lomi.africa`](mailto:conduct@lomi.africa) (This address reaches designated moderators). * **GitHub**: If comfortable, open a confidential issue in a private repository (details provided upon request to the email above) or directly message a maintainer. Please **do not** report CoC violations via public `GitHub` issues. * **Discord**: Send a direct message to users with the `@Moderator` role in our [Discord server](https://discord.gg/33syDfh9). ### Report content To help us address the issue effectively, please include as much detail as possible: ```markdown filename="Incident Report Template" ## Code of conduct incident report - **Your contact info (optional but helpful):** [Your name/email] - **Date/time of incident:** [Approximate time and timezone] - **Location of incident:** [e.g., GitHub issue link, Discord channel name, etc.] - **Description of behavior:** [Detailed account of what happened] - **Individuals involved:** [Names/usernames of people involved, including witnesses if any] - **Supporting evidence:** [Links, screenshots, logs, etc.] - **Desired outcome (optional):** [What resolution are you hoping for?] ``` ### Confidentiality and response We will maintain confidentiality to the extent possible while investigating. Retaliation against reporters is a violation of this Code of Conduct. * **Acknowledgment**: We aim to acknowledge receipt of reports within 24-48 business hours. * **Investigation**: The duration will depend on the complexity, but we strive to conclude investigations within 1-2 weeks. * **Resolution**: We will communicate the outcome and any actions taken to the reporter, where appropriate. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1, available at [`https://www.contributor-covenant.org/version/2/1/code_of_conduct.html`](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). For answers to common questions about this code of conduct, see the FAQ at [`https://www.contributor-covenant.org/faq`](https://www.contributor-covenant.org/faq). Best practices Branching strategy Getting support # Code reviews Source: https://docs.lomi.africa/resources/contributing/code-reviews Our code review process ensures code quality, knowledge sharing, and maintainability. *** title: 'Code reviews' description: 'Our code review process ensures code quality, knowledge sharing, and maintainability.' ---------------------------------------------------------------------------------------------------- ## Pull request guidelines ### Title format ```bash filename="PR Title Format" # Format (): # Examples feat(payments): implement Wave payment provider fix(webhooks): handle timeout errors docs(api): update authentication guide ``` ### Description template ```markdown filename="PR Description Template" ## Changes - Added Wave payment provider integration - Implemented webhook signature verification - Updated API documentation ## Testing - Unit tests added for payment processing - Integration tests for webhook handling - Manual testing with test credentials ## Screenshots [If applicable] ## Related issues Closes #123 ``` ## Review process ### Self review ```bash filename="Terminal - Self Review Checks" # Run tests npm test # Check linting npm run lint # Build documentation npm run docs:build ``` ### Code review * Request reviews from relevant team members * Address feedback promptly * Re-request review after changes ### CI checks * All tests must pass * Code coverage requirements met * No security vulnerabilities * Documentation updated ## What to review ### Code quality * Follows coding standards * No duplicate code * Proper error handling * Efficient implementation ### Testing * Unit tests added/updated * Integration tests if needed * Edge cases covered * Test coverage maintained ### Security * Input validation * Authentication/Authorization * Sensitive data handling * Security best practices ### Documentation * Code comments * API documentation * README updates * Changelog entry ## Best practices ### As a submitter ```typescript filename="Submitter Example (Good vs Bad)" // DO: Small, focused changes function validatePayment(amount: number): boolean { return amount > 0 && amount <= 1000000; } // DON'T: Multiple unrelated changes /* function validateAndProcessPayment() { // Mixed concerns } */ ``` ### As a reviewer ```typescript filename="Reviewer Example (Good vs Bad Feedback)" // Good feedback // Consider using a type guard for better type safety function isValidAmount(amount: unknown): amount is number { return typeof amount === 'number' && amount > 0; } // Unhelpful feedback // // This is wrong ``` ### Code examples ```typescript filename="Code Example (Before/After)" // Before /* function process(data) { if (data) { return data.value; } } */ // After function process(data: InputData): OutputData { if (!data) { throw new Error('Data is required'); } return data.value; } ``` ## Review comments ### Constructive feedback ```typescript filename="Constructive Feedback Example" // Instead of: // // This is messy // Better: // Consider extracting this logic into a separate function // for better reusability and testing: function validateWebhookSignature(payload: string, signature: string): boolean { // Implementation } ``` ### Suggestions ```typescript filename="Suggestion Example" // Instead of: // // Use better names // Better: // Consider more descriptive names: // - `processPayment` -> `validateAndProcessPayment` // - `data` -> `paymentData` ``` ## After review ### Addressing feedback ```bash filename="Terminal - Addressing Feedback" # Update branch git fetch origin git rebase origin/develop # Make changes git add . git commit -m "fix: address review feedback" # Force push if needed git push --force-with-lease ``` ### Merging ```bash filename="Terminal - Merging Strategies" # Squash and merge git checkout develop git merge --squash feature/payment-method # Or rebase and merge git checkout develop git rebase feature/payment-method ``` Best practices Branching strategy Versioning # Contributing Source: https://docs.lomi.africa/resources/contributing Learn how to contribute to lomi. through code contributions, documentation improvements, and community engagement. *** title: Contributing description: Learn how to contribute to lomi. through code contributions, documentation improvements, and community engagement. index: true ----------- ## Roles: developer vs contributor vs operator | Role | Who | CLI / MCP | | ---------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | **Developer (integrator)** | Engineer at a merchant building on lomi. | **Yes**: primary audience for [CLI](/build/cli) and [MCP](/build/mcp) | | **Merchant (non-technical)** | Dashboard-only user | **No**: use the web dashboard | | **Contributor** | Open-source contributor to the monorepo | **Sometimes**: dogfood integration flows; maintain packages via [Maintaining CLI and MCP](/resources/contributing/maintaining-cli-mcp) | | **Platform admin** | Internal `apps/admin` operators | **No** | **Rule:** CLI and MCP help you **integrate** with the hosted merchant API. They do not operate lomi.’s platform, admin stack, or payment infrastructure. # Maintaining CLI and MCP Source: https://docs.lomi.africa/resources/contributing/maintaining-cli-mcp Guide for lomi. contributors and operators who develop or deploy apps/cli and apps/mcp, not for merchant integrators. *** title: 'Maintaining CLI and MCP' description: 'Guide for lomi. contributors and operators who develop or deploy apps/cli and apps/mcp, not for merchant integrators.' ------------------------------------------------------------------------------------------------------------------------------------ The **lomi. CLI** and **MCP server** are **integrator tools**: they help developers build on the hosted merchant API. This page is for people who **maintain those packages** in the monorepo or deploy the hosted MCP server. If you are integrating lomi. into your product, use [CLI](/build/cli) and [MCP](/build/mcp) instead. ## Who should read this | Role | Use CLI/MCP to… | Use this page for… | | ---------------------- | ------------------------------------------------- | ---------------------------------------------- | | **Merchant developer** | Integrate checkout, webhooks, SDK | No, use Build docs | | **Contributor** | Dogfood integration flows while building features | Yes, when changing `apps/cli` or `apps/mcp` | | **Operator** | - | Yes, deploy MCP, regenerate tools, release CLI | CLI/MCP are **not** for operating `apps/admin`, self-hosting payment processing, or general monorepo maintenance (migrations, API deploy, etc.). ## CLI (`apps/cli`) Maintainer docs: [`apps/cli/CONTRIBUTING.md`](https://github.com/lomiafrica/lomi./blob/master/apps/cli/CONTRIBUTING.md) | Task | Command / location | | ------------------- | ----------------------------------------------------------------------- | | Local dev | `cd apps/cli && cargo build && cargo test` | | Docs drift | `lomi docs check` or `dt check` (from monorepo root) | | Docs scan / graph | `lomi docs scan`, `lomi docs graph` (delegates to `apps/tools/doctool`) | | Docs i18n | `lomi docs sync-i18n`, `lomi docs translate-i18n` | | Docs improve / diff | `lomi docs improve`, `lomi docs diff` | | Agent rules | `./apps/cli/scripts/generate/generate-rules.sh` | | Release | Tag `cli-v*`, see CONTRIBUTING.md | ## doctool (`dt`) Documentation tooling CLI (CORE-38). **Standalone repo:** [github.com/lomiafrica/doctool](https://github.com/lomiafrica/doctool), checked out as a submodule at `apps/tools/doctool`. Initialize for local dev: ```bash git submodule update --init apps/tools/doctool cd apps/tools/doctool && cargo build ``` Maintainer docs: [`apps/tools/doctool/CONTRIBUTING.md`](https://github.com/lomiafrica/doctool/blob/main/CONTRIBUTING.md) | Task | Command / location | | ------------------------- | --------------------------------------------------------------------------------- | | Local dev | `cd apps/tools/doctool && cargo build && cargo test` | | Index monorepo | `dt scan --root .` → `.doctool/index.json` | | Drift report | `dt drift --root .` | | Knowledge graph | `dt graph --root .` | | Scaffold REST MDX | `dt scaffold --root .` | | i18n sync (deterministic) | `dt sync-i18n --check` | | i18n translate (LLM) | `dt translate-i18n --dry-run` (set `DOCTOOL_LLM_API_KEY` or `DOCTOOL_LLM_MOCK=1`) | | Improve MDX | `dt improve --path build/usage-billing.mdx --stdout` | | Diff proposed MDX | `dt diff --path build/usage-billing.mdx --proposed .doctool/out/...` | | CI | `.github/workflows/app-ci-doctool.yml` | ## MCP (`apps/mcp`) Maintainer docs: [`apps/mcp/CONTRIBUTING.md`](https://github.com/lomiafrica/lomi./blob/master/apps/mcp/CONTRIBUTING.md) | Task | Command / location | | ------------------------- | --------------------------------------------------------------------------------------------------------------- | | Local HTTP server | `cd apps/mcp && pnpm run start:http` | | Regenerate tools | `pnpm run generate` → commit `src/generated/tools-manifest.json` | | Drift CI | `pnpm docs:drift` (OpenAPI ↔ MDX, MCP manifest, agent contracts) | | Agent OpenAPI | `cd apps/api && pnpm run openapi:export:agent` → `apps/docs/agent-openapi.json` (agent, provisioning, partners) | | OAuth / agent connect env | API: `LOMI_OAUTH_ISSUER`, `LOMI_DASHBOARD_BASE_URL`: MCP: `LOMI_OAUTH_ISSUER`, `LOMI_MCP_RESOURCE_URL` | | Deploy | `.env.example`, `railway.json` | | Tests | `pnpm test` | | Agent plugin | `node apps/tools/agent-plugin/scripts/validate.mjs` | Integrators should use `https://mcp.lomi.africa` or `npx @lomi./mcp`, not a self-deployed instance unless they work on lomi. engineering. The [agent plugin](https://github.com/lomiafrica/agent-plugin) is marketplace packaging only. How to contribute Writing for lomi. docs Open source # How to contribute Source: https://docs.lomi.africa/resources/contributing/overview This section provides guidelines and best practices to help you get started with contributing to lomi.. project. *** title: 'How to contribute' description: 'This section provides guidelines and best practices to help you get started with contributing to lomi.. project.' ------------------------------------------------------------------------------------------------------------------------------- We're thrilled you're interested in helping improve lomi. Whether you're fixing bugs, adding new features, improving the documentation, or reporting issues, your contributions are valuable and we are grateful for your help. ## How to contribute There are many ways to contribute: * **Reporting bugs**: If you find a bug, please report it on [GitHub Issues](https://github.com/lomiafrica/lomi./issues). * **Suggesting enhancements**: Have an idea for a new feature or improvement? Share it via [GitHub Issues](https://github.com/lomiafrica/lomi./issues). * **Writing code**: Contribute directly to the codebase by fixing bugs or implementing new features. See the [Best practices](/resources/contributing/best-practices) guide. * **Improving the docs**: Help us make our documentation clearer and more comprehensive. * **Community support**: Answer questions and help other users in the [Discord community](https://discord.gg/33syDfh9). ### Links Before you start, please familiarize yourself with these important documents: * **[Code of Conduct](/resources/contributing/code-of-conduct)**: Our expectations for behavior within the community. * **[Best practices](/resources/contributing/best-practices)**: Detailed steps for contributing code and documentation. * **[Branching strategy](/resources/contributing/branching-strategy)**: How we manage branches for development and releases. We appreciate your interest and look forward to hearing from you! *** # Getting started Ready to make your first contribution to lomi.? This guide will walk you through finding an issue, setting up your development environment, making changes, and submitting them. ## Prerequisites Before you begin, ensure you have the following installed: * **[Git](https://git-scm.com/)**: For version control. * **[Node.js](https://nodejs.org/)**: We recommend the latest LTS version. * **[bun](https://bun.sh/)**: Our preferred package manager for managing dependencies in the monorepo. ### Find an issue to work on Before diving into code, it's helpful to find an existing issue or propose a new one: * Browse [open issues](https://github.com/lomiafrica/lomi./issues) on the main repository. * Look for issues tagged with `good first issue` if you're new. * If you have a new idea or bug fix, consider creating a new issue to discuss it first. * Don't hesitate to ask questions in the issue comments if anything is unclear. ### Fork the repository Start by forking the main lomi. repository on GitHub: 1. Go to the [lomi. repository](https://github.com/lomiafrica/lomi.). 2. Give us a star ! 3. Click the "Fork" button in the top-right corner. ### Clone your fork Clone your forked repository to your local machine: ```bash filename="Terminal" git clone https://github.com/beloved_anon/lomi.git cd lomi. ``` Replace `` with your real GitHub username. ### Set upstream remote Add the original lomi. repository as the `upstream` remote. This allows you to keep your fork synchronized with the main project. ```bash filename="Terminal" git remote add upstream https://github.com/lomiafrica/lomi./ ``` Verify the remotes: ```bash filename="Terminal" git remote -v # origin https://github.com//lomi./ (fetch) # origin https://github.com//lomi./ (push) # upstream https://github.com/lomiafrica/lomi./ (fetch) # upstream https://github.com/lomiafrica/lomi./ (push) ``` ### Install dependencies & set up environment Install the project dependencies using `bun` from the root of the monorepo: ```bash filename="Terminal" bun install ``` There is no root `.env.example`. Copy the example file for the app you are working on: ```bash filename="Terminal" # API service cp apps/api/.env.example apps/api/.env.local # Documentation site cp apps/docs/.env.example apps/docs/.env # Other apps: see each app's README (e.g. apps/dashboard/README.md) ``` Review each file and add secrets from your team or sandbox dashboard. ### Local development commands Here are some common commands you'll use during development: ```bash filename="Terminal" # From monorepo root: starts the dashboard dev server only (contributors). # For other apps, cd into apps/ and use that app's package.json scripts. bun run dev # Build and serve the documentation site locally bun run docs:dev # Run linting checks across the project bun run lint # Run linters and automatically fix issues bun run lint:fix ``` ### Create a branch Before making changes, create a new branch based on the `develop` branch. Follow our [Branching strategy](/resources/contributing/branching-strategy) for naming conventions. ```bash filename="Terminal" # Fetch the latest changes from upstream git fetch upstream # Check out the develop branch git checkout develop # Pull the latest changes for develop git pull upstream develop # Create your feature or bugfix branch git checkout -b / # Example: git checkout -b feature/add-cool-new-thing # Example: git checkout -b fix/resolve-that-bug ``` ### Make your changes Now you can start making your code or documentation changes within the appropriate package or app in the `packages/` or `apps/` directory. * **Code changes**: Write your code, ensuring it adheres to our [Code standards](/resources/contributing/best-practices#code-standards) and [Best practices](/resources/contributing/best-practices). * **Documentation changes**: Update or add documentation under `apps/docs/content/docs/`. Use Markdown (`.mdx`). **Keep your branch updated:** Periodically, keep your branch up-to-date with the latest changes from the upstream `develop` branch: ```bash filename="Terminal" # Fetch the latest changes from upstream git fetch upstream # Rebase your branch onto the latest develop branch # Make sure you have committed or stashed your local changes first! git rebase upstream/develop # You might need to resolve conflicts during the rebase process. # After resolving conflicts: git add . ; git rebase --continue # If you get stuck: git rebase --abort ``` ### Test your changes Ensure your changes pass all relevant tests and meet our quality standards. ```bash filename="Terminal" # Example: Running tests for a specific package # Replace with the actual package, e.g., @lomi/api bun --filter test # Run specific tests using a pattern (e.g., tests related to 'payment') bun test -- --grep "payment" # Run all tests across the monorepo bun test # Run tests and generate a coverage report bun run test:coverage ``` Write tests for your changes. See the [Code standards](/resources/contributing/best-practices#code-standards) section for an example. ### Commit your changes Commit your changes using a descriptive message that follows our [Commit message guidelines](/resources/contributing/branching-strategy). Adhering to the format helps automate releases and changelogs. **Commit message format:** ```text filename="Commit Message Format" (): # Examples: # feat(api): add support for webhook signature verification # fix(docs): correct typo in getting started guide # chore(deps): update dependency xyz ``` **Example commit:** ```bash filename="Terminal" git add . git commit -m "feat(payments): implement new payment provider" ``` See [Best practices](/resources/contributing/best-practices) for good vs. bad commit message examples. ### Push your changes Push your branch to your fork on GitHub: ```bash filename="Terminal" git push origin ``` ### Submit a pull request (PR) 1. Go to your fork on GitHub (`https://github.com//lomi.`). 2. You should see a prompt to create a Pull Request from your recently pushed branch. Click "**Compare & pull request**". 3. Ensure the base repository is `lomiafrica/lomi.` and the base branch is `develop`. 4. The head repository should be your fork, and the compare branch should be your feature/fix branch. 5. **Write a clear PR title:** Use the same format as commit messages (`(): `). 6. **Fill out the PR template:** Provide a clear description of your changes, including: * **Changes:** What did you change and why? * Code follows the style guidelines of this project. * You performed a self-review of your own code. * Hard-to-understand areas are commented. * Documentation is updated. * Changes generate no new warnings. * Tests prove the fix is effective or the feature works. * New and existing unit tests pass locally (`bun test`). * Dependent changes are merged and published in downstream modules. * Commits are squashed when needed and have meaningful messages. 7. Submit the Pull Request. ## Review process Your PR will be reviewed by maintainers. ### During review: * **Respond to Feedback:** Address comments and questions promptly. * **Make Changes:** Push new commits to your PR branch to incorporate requested changes. Avoid force-pushing unless asked. * **Keep Updated:** If `develop` advances significantly, rebase your branch to resolve conflicts. ### After merge: * **Celebrate!** 🎉 Thank you for your contribution. * **Clean Up:** You can safely delete your feature branch from your fork. * **Stay involved:** Keep an eye on related issues or follow up if needed. Monitor the deployment if applicable. ## Product surface parity (API, docs, website) When you change the public merchant API or add a marketing product page, keep these surfaces aligned: 1. **Export OpenAPI** from `apps/api`: `pnpm openapi:export:all` (commits `apps/docs/openapi.json` and `agent-openapi.json`). 2. **Docs drift**: `cd apps/docs && pnpm docs:drift`. 3. **Parity worker** (full gate): `cd apps/docs && pnpm parity` (allowlist, build sidebar, website mirrors, product surface, MCP manifest). 4. **Website mirrors**: `cd apps/website && node scripts/build/sync-openapi.mjs`, then commit `public/openapi.json` and `public/agent-openapi.json` in the `lomiafrica/website` repo and bump the monorepo submodule pointer. 5. **MCP tools** (if allowlist changed): `cd apps/mcp && pnpm generate`. CI runs `.github/workflows/app-parity.yml` on API/docs/website changes; a weekly cron posts failures to `#devops` when `SLACK_WEBHOOK_URL` is set. Code of conduct Branching strategy Code reviews # Using git Source: https://docs.lomi.africa/resources/contributing/using-git Use Git from the command line when collaborating on your lomi. integration or open-source contributions. *** title: 'Using git' description: 'Use Git from the command line when collaborating on your lomi. integration or open-source contributions.' ----------------------------------------------------------------------------------------------------------------------- ## Initializing a Git repository To get started, initialize a Git repository in the directory where your integration code and configuration files are stored: ```bash filename="Terminal" git init ``` ## Staging and committing changes After making changes to your integration code or configurations, stage the changes using the `git add` command: ```bash filename="Terminal" git add . ``` Then, commit the changes with a descriptive message: ```bash filename="Terminal" git commit -m "Update payment provider configuration" ``` ## Pushing changes to a remote repository To collaborate with your team, push your changes to a remote repository: ```bash filename="Terminal" git push origin develop ``` Replace `develop` with the appropriate branch name if you're using a different branching strategy. ## Pulling changes from a remote repository To get the latest changes made by your team members, pull the changes from the remote repository: ```bash filename="Terminal" git pull origin develop ``` Again, replace `develop` with the appropriate branch name if necessary. ## Resolving merge conflicts If you encounter merge conflicts when pulling changes, you'll need to resolve them manually. Open the conflicting files, make the necessary changes, and then stage and commit the resolved files: ```bash filename="Terminal" git add resolved_file.js git commit -m "Resolve merge conflicts" ``` ## Best practices * Commit frequently with descriptive messages * Use branches for developing new features or configurations * Regularly pull changes from the remote repository to stay up-to-date * Resolve merge conflicts carefully and communicate with your team By using Git effectively and following best practices, you can collaborate smoothly on your lomi. integration. # Versioning Source: https://docs.lomi.africa/resources/contributing/versioning We follow Semantic Versioning (SemVer) for all our packages and APIs. *** title: 'Versioning' description: 'We follow Semantic Versioning (SemVer) for all our packages and APIs.' ------------------------------------------------------------------------------------ ## Version format ```bash filename="SemVer Format" MAJOR.MINOR.PATCH # Example: 1.2.3 ``` * **MAJOR**: Breaking changes * **MINOR**: New features (backward compatible) * **PATCH**: Bug fixes (backward compatible) ## Version management ### NPM version ```bash filename="Terminal - npm version" # Patch release npm version patch # 1.2.3 -> 1.2.4 # Minor release npm version minor # 1.2.3 -> 1.3.0 # Major release npm version major # 1.2.3 -> 2.0.0 ``` ### Git tags ```bash filename="Terminal - git tag" # Create annotated tag git tag -a v1.2.3 -m "Version 1.2.3" # Push tags git push origin --tags ``` ## Release process 1. **Update version** ```bash filename="Terminal - Update Version" # Update package.json npm version minor # Update changelog git cliff -o CHANGELOG.md # Commit changes git add CHANGELOG.md git commit -m "chore: update changelog" ``` 2. **Create release** ```bash filename="Terminal - Create Release" # Create GitHub release gh release create v1.2.3 \ --title "Version 1.2.3" \ --notes-file CHANGELOG.md ``` ## API versioning ### URL versioning ```bash filename="API URL Versioning Example" # Current version https://api.lomi.africa/checkout/sessions # Future version https://api.lomi.africa/v2/checkout/sessions ``` ### Version lifecycle 1. **Active** * Latest version * Full support * Regular updates 2. **Maintained** * Previous version * Security updates * Bug fixes only 3. **Deprecated** * Old version * Limited support * Migration required ## Breaking changes 1. **Notification** * Advance notice (minimum 6 months) * Migration guide * Deprecation warnings 2. **Documentation** * Version comparison * Migration steps * Code examples 3. **Support** * Migration assistance * Legacy version support * Transition period ## Version control ### Package files ```json filename="package.json Example" { "name": "@lomi/sdk", "version": "1.2.3", "engines": { "node": ">=14" } } ``` ### Lock files ```bash filename="Lockfile Examples" # NPM package-lock.json # Yarn yarn.lock ``` ## Best practices 1. **Version numbers** * Use semantic versioning * Document changes * Keep changelog updated 2. **Dependencies** * Pin exact versions * Regular updates * Security audits 3. **Release notes** * Clear descriptions * Breaking changes * Upgrade guide Best practices API reference Changelog # Writing the docs Source: https://docs.lomi.africa/resources/contributing/writing-for-lomi-docs Editorial contract for hand-authored guides, API pages, and agent-oriented documentation on docs.lomi.africa. *** title: Writing the docs description: Editorial contract for hand-authored guides, API pages, and agent-oriented documentation on docs.lomi.africa. docType: explanation -------------------- import { Callout } from '@/components/docs/docs-callout'; This page is the **canonical writing contract** for lomi. developer documentation. It merges the API style contract, Diátaxis doc types, and brand rules used across the monorepo. Agents: install the condensed rule with `lomi install-rules --target cursor` (includes **Docs writing**). Always read [llms.txt](https://docs.lomi.africa/llms.txt) before integrating. ## Brand and naming | Context | Form | | ---------------- | ---------------------------------------------------------------------- | | Product in prose | **lomi.** (with trailing dot) | | npm package | `@lomi./sdk` | | Hostnames | `lomi.africa`, `docs.lomi.africa`, `api.lomi.africa` (no trailing dot) | ## Diátaxis doc types Set `docType` in frontmatter on new guide pages: | Type | Use for | Examples | | ------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------- | | `tutorial` | Linear learning path | [Integration journey](/start/integration-journey) | | `how-to` | Task-focused steps | [Verify payments](/build/reliability/verify-payments) | | `explanation` | Concepts and trade-offs | [Choose integration](/build/choose-integration), [Payment and payout lifecycle](/build/reliability/payment-lifecycle) | | `reference` | Schemas and endpoints | API operation pages | ## Voice and structure * **Direct and confident**: "Creates…", "Returns…", not "This endpoint allows you to…". * **Merchant-centric**: status codes, idempotency, async mobile money, webhook requirements. * **Question-led titles** for Build guides when possible. * **No filler**: avoid "seamless", "robust", "powerful" without a concrete behavior. * **No em dashes** in prose: use commas, colons, or periods instead (table empty cells may use `-`). Hand-authored guides should link to canonical references instead of duplicating tables (e.g. point to [Sandbox payments](/start/sandbox-payments) for test cards). Page ends use the built-in previous / next footer only. Do not add a custom next-steps card grid or a markdown `## Next steps` list. ## Integration truths (repeat in guides) * Amounts in **XOF** use **centimes** (integer minor units) unless a specific API field documents otherwise. * **API key determines environment**: `lomi_sk_test_…` vs `lomi_sk_live_…`; wrong key against an environment returns **401**. * **Live mobile money is asynchronous**: confirm with webhooks and `GET /transactions/{id}` before fulfilling orders. * **Never trust client-only success**: see [Verify payments](/build/reliability/verify-payments). ## Bilingual policy Every English page under `content/docs/` (`.mdx` without a locale suffix) requires a **French** sibling (`.fr.mdx`). Spanish (`.es.mdx`) and Chinese (`.zh.mdx`) pages are optional locale variants. `pnpm docs:drift` enforces EN/FR parity. ## Generated REST pages See [API reference authoring](/resources/contributing/api-reference-authoring) and `apps/docs/lib/scripts/manual-api/DOC-STYLE-CONTRACT.md` for operation page structure, banned OpenAPI phrasing, and `EN_OPERATION_COPY` maintenance. ## Anchor pages to imitate * [Sandbox payments](/start/sandbox-payments) * [Choose integration](/build/choose-integration) * [Handling webhooks](/build/reliability/handling-webhooks) * [Payment and payout lifecycle](/build/reliability/payment-lifecycle) When you change payment status semantics in `apps/api`, update [Payment and payout lifecycle](/build/reliability/payment-lifecycle) and run `pnpm docs:drift` in `apps/docs`. ## Drift checks Before merging docs changes: ```bash cd apps/docs && pnpm lint && pnpm docs:drift ``` From the monorepo root: ```bash lomi docs check ``` Install agent rules How to contribute