curl --request POST \
--url https://api.bachs.io/v1/checkout-sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"customer": {
"email": "customer@example.com",
"name": "John Doe"
},
"product_cart": [
{
"product_id": "prod_abc123"
}
],
"payment_method_types": [
"USD_CARD",
"NGN_BANK_TRANSFER"
]
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
customer: {email: 'customer@example.com', name: 'John Doe'},
product_cart: [{product_id: 'prod_abc123'}],
payment_method_types: ['USD_CARD', 'NGN_BANK_TRANSFER']
})
};
fetch('https://api.bachs.io/v1/checkout-sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.bachs.io/v1/checkout-sessions"
payload = {
"customer": {
"email": "customer@example.com",
"name": "John Doe"
},
"product_cart": [{ "product_id": "prod_abc123" }],
"payment_method_types": ["USD_CARD", "NGN_BANK_TRANSFER"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.bachs.io/v1/checkout-sessions"
payload := strings.NewReader("{\n \"customer\": {\n \"email\": \"customer@example.com\",\n \"name\": \"John Doe\"\n },\n \"product_cart\": [\n {\n \"product_id\": \"prod_abc123\"\n }\n ],\n \"payment_method_types\": [\n \"USD_CARD\",\n \"NGN_BANK_TRANSFER\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"checkout_id": "chk_1M2N3o4P5q6R7s8T",
"checkout_url": "https://checkout.bachs.io/c/Tb5rHn8YkQ2vXpL",
"status": "open",
"expires_at": "2026-01-24T15:30:00.000Z",
"created_at": "2026-01-24T14:30:00.000Z",
"reference": "order_9876"
}{
"detail": "Invalid request parameters",
"error_code": "VALIDATION_ERROR",
"errors": [
{
"field": "amount",
"message": "Amount must be a positive decimal string",
"type": "value_error"
}
]
}{
"detail": "Invalid API key",
"error_code": "UNAUTHORIZED"
}{
"detail": "API key does not have permission for this operation",
"error_code": "FORBIDDEN"
}{
"detail": "Resource not found",
"error_code": "NOT_FOUND"
}{
"detail": "Rate limit exceeded. Please retry after a few seconds.",
"error_code": "TOO_MANY_REQUESTS"
}{
"detail": "An unexpected error occurred. Please try again later.",
"error_code": "INTERNAL_SERVER_ERROR"
}Create a checkout session
Create a product-based checkout session
curl --request POST \
--url https://api.bachs.io/v1/checkout-sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"customer": {
"email": "customer@example.com",
"name": "John Doe"
},
"product_cart": [
{
"product_id": "prod_abc123"
}
],
"payment_method_types": [
"USD_CARD",
"NGN_BANK_TRANSFER"
]
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
customer: {email: 'customer@example.com', name: 'John Doe'},
product_cart: [{product_id: 'prod_abc123'}],
payment_method_types: ['USD_CARD', 'NGN_BANK_TRANSFER']
})
};
fetch('https://api.bachs.io/v1/checkout-sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.bachs.io/v1/checkout-sessions"
payload = {
"customer": {
"email": "customer@example.com",
"name": "John Doe"
},
"product_cart": [{ "product_id": "prod_abc123" }],
"payment_method_types": ["USD_CARD", "NGN_BANK_TRANSFER"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.bachs.io/v1/checkout-sessions"
payload := strings.NewReader("{\n \"customer\": {\n \"email\": \"customer@example.com\",\n \"name\": \"John Doe\"\n },\n \"product_cart\": [\n {\n \"product_id\": \"prod_abc123\"\n }\n ],\n \"payment_method_types\": [\n \"USD_CARD\",\n \"NGN_BANK_TRANSFER\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"checkout_id": "chk_1M2N3o4P5q6R7s8T",
"checkout_url": "https://checkout.bachs.io/c/Tb5rHn8YkQ2vXpL",
"status": "open",
"expires_at": "2026-01-24T15:30:00.000Z",
"created_at": "2026-01-24T14:30:00.000Z",
"reference": "order_9876"
}{
"detail": "Invalid request parameters",
"error_code": "VALIDATION_ERROR",
"errors": [
{
"field": "amount",
"message": "Amount must be a positive decimal string",
"type": "value_error"
}
]
}{
"detail": "Invalid API key",
"error_code": "UNAUTHORIZED"
}{
"detail": "API key does not have permission for this operation",
"error_code": "FORBIDDEN"
}{
"detail": "Resource not found",
"error_code": "NOT_FOUND"
}{
"detail": "Rate limit exceeded. Please retry after a few seconds.",
"error_code": "TOO_MANY_REQUESTS"
}{
"detail": "An unexpected error occurred. Please try again later.",
"error_code": "INTERNAL_SERVER_ERROR"
}Authorizations
Bearer token authentication. Pass your API key as Authorization: Bearer sk_.... See Authentication for keys, scopes, and sandbox vs production.
Body
- Option 1
- Option 2
Checkout with a cart of catalog products (each item may override its price via pricing).
Catalog products to include in this checkout session. Mutually exclusive with pricing.
1 - 20 elementsShow child attributes
Show child attributes
Optional checkout billing currency. If omitted, defaults to product pricing currency.
"USD"
Restricts the checkout to specific payment methods. Values are exact payment-method corridors, not payment types: USD_CARD and NGN_CARD are separate corridors, as is each of the nine mobile money corridors. Valid values: USD_CARD (US card, USD), NGN_CARD (Nigerian card, NGN), NGN_BANK_TRANSFER (Nigerian bank transfer, NGN), MOMO_GHS (Ghana mobile money), MOMO_KES (Kenya mobile money), MOMO_TZS (Tanzania mobile money), MOMO_UGX (Uganda mobile money), MOMO_XAF (Central Africa CFA mobile money), MOMO_XOF (West Africa CFA mobile money), MOMO_RWF (Rwanda mobile money), MOMO_MWK (Malawi mobile money), MOMO_ZMW (Zambia mobile money), and CRYPTO (all supported crypto assets). A corridor you leave out is not offered. Restricting only narrows what the customer sees: it never adds a corridor your account is not already enabled for. If the restriction leaves no payable method, the request is rejected.
["USD_CARD", "NGN_BANK_TRANSFER"]
Where to send the customer if they cancel or abandon the checkout. Returned on the checkout so the hosted page can route back to it.
"https://shop.example.com/cart"
Deprecated alias for success_url, kept for backward compatibility. If both are set, success_url wins.
"https://shop.example.com/thanks"
Where to redirect the customer after a successful payment. Bachs appends ?checkout_id=<id>. This is the primary success-redirect field.
"https://shop.example.com/success"
Customer for the checkout session, optional. Omit it and the hosted checkout page collects the buyer's email and name instead, recording them on customer_details. Send customer_creation: always to also create a customer record from what they give. Required for a subscription checkout, which has no later opportunity to collect it.
- Option 1
- Option 2
Show child attributes
Show child attributes
Optional metadata (max 20 keys, max 10KB total).
Raw pricing for a product-less (pure) checkout. Mutually exclusive with product_cart.
Show child attributes
Show child attributes
The platform's cut of this sale, in the base currency of the sale, taken from the merchant's proceeds rather than from Bachs's processing fee. On a destination charge, this is one of two ways to state the split: the account receives the gross minus this amount. Mutually exclusive with transfer_data.amount. A destination charge needs one of the two; a direct charge can set this alone to move part of its own charge up to the platform. See Platform fees.
"20000.00"
Names the account this checkout pays out to. Its presence, on its own, is what makes this a destination charge belonging to your platform rather than the account. A destination charge needs a split term: either platform_fee on the request root, or transfer_data.amount here. Omit transfer_data entirely, and act as the account with X-Account-Id instead, for a direct charge. See Destination charges.
Show child attributes
Show child attributes
Your own reference for this session, unique per account. Omit it and the session has none; use the session's id to track it.
128"order_9876"
Minutes until the checkout session expires. Defaults to 60. After expiry the checkout URL is invalid.
1 <= x <= 144060
Whether a buyer who identifies themselves on the hosted page also becomes a customer record. Applies only when you omit customer. if_required (default) keeps them out of your directory: customer stays null, no customer.created or customer.updated webhook fires, and their email and name reach you on customer_details instead. Their purchases still group together in your dashboard. always adds them to your directory, matched by email to a customer you already hold where one exists, and returns it on customer. That match is on the email alone, and an email typed on the checkout page is not verified, so a buyer who knows one of your customers' addresses has their purchase recorded against that customer. Ignored for a subscription or setup checkout, which always create a customer. See Whether a guest becomes a customer.
always, if_required "if_required"
Save the customer's card so you can charge it later without them present. Send it with a price and the customer pays now and the card is kept. Send it with no pricing and the checkout collects a card and charges nothing, which needs an existing customer (customer.customer_id) for the card to belong to. Only cards can be charged again, so a checkout that offers none is refused with CHECKOUT_CANNOT_SAVE_PAYMENT_METHOD. Saving cards is in beta and this might change.
Response
Success - Checkout session created successfully
Response containing checkout session details and hosted checkout URL.
Unique identifier for the underlying checkout.
"5d7ab015-5886-4a1e-89bb-abe499d0b8ee"
Hosted checkout URL where your customer can complete payment.
"https://checkout.bachs.io/c/Mz9wDp3sVn7QaTf"
Current checkout status. open: awaiting customer payment, where every new session starts. completed: payment succeeded, a terminal state. expired: the session window elapsed before payment, a terminal state. cancelled: cancelled before completion, a terminal state.
open, completed, expired, cancelled "open"
ISO 8601 timestamp indicating when the checkout will expire. After this time, customers cannot complete payment through this checkout.
"2026-01-24T15:30:00.000Z"
ISO 8601 timestamp indicating when the checkout was created.
"2026-01-24T14:30:00.000Z"
Your own reference for this checkout, echoed back unchanged. null when you did not supply one.
"order_9876"
The platform's cut of this sale, echoed back from the request, in the base currency of the sale. The key is always present; it reads null, not "0.00", on a checkout that carries no fee, and on a checkout that split the sale with transfer_data.amount instead. See Platform fees.
"20000.00"
The seller's contracted share of this sale, echoed back from transfer_data.amount, in the base currency of the sale. Null on a checkout that carries no split, and on one that split the sale with platform_fee instead.
"80000.00"

