. Create an API key under API keys.API reference · Account
Billing
Plans, prices, checkout and invoices.
List plans and prices
/v1/billing/plansPlans with their price in every currency they are sold in. All amounts are integers in minor units (cents; IDR has no minor unit), e.g. {"IDR": 99000, "USD": 900}.
Responses
- 200Plans
Response fields
plansarray of objectrequiredplans[].idintegerrequiredplans[].namestringrequiredplans[].price_monthlyintegerrequiredPrice in IDRplans[].max_sessionsintegerrequiredplans[].max_messages_per_monthintegerrequired0 = unlimitedplans[].send_per_minuteintegerrequiredRequests per minute the plan allows onPOST …messages…(seeGET /v1/usage)plans[].ai_replies_per_monthintegerrequiredAI auto-replies included per month (0 = none)plans[].pricesobjectrequiredannual_months_chargedintegerrequiredMonths charged for an annual period (12 months delivered)
curl "https://api.chatkomodo.com/v1/billing/plans" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/billing/plans', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/billing/plans",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/billing/plans', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"plans": [
{
"id": 1,
"name": "Example",
"price_monthly": 150000,
"max_sessions": 1,
"max_messages_per_month": 1,
"send_per_minute": 1,
"ai_replies_per_month": 1,
"prices": {}
}
],
"annual_months_charged": 1
}List payment methods
/v1/billing/optionsHow this tenant can pay: only payment methods that are set up are listed. default_provider is Pakasir (QRIS, bank, e-wallet; IDR) for Indonesian tenants and the cheapest international method otherwise. currency is what each provider would charge for the tenant's plan; it is null when the plan has no price in a currency that provider accepts.
Responses
- 200Payment options
Response fields
default_providerstring | nullrequiredtenant_currencystringrequiredprovidersarray of objectrequiredproviders[].idstringrequiredproviders[].labelstringrequiredproviders[].currenciesarray of stringrequiredproviders[].currencystring | nullrequiredISO 4217 code, e.g.USD,IDR
curl "https://api.chatkomodo.com/v1/billing/options" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/billing/options', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/billing/options",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/billing/options', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"default_provider": "pakasir",
"tenant_currency": "IDR",
"providers": [
{
"id": "pakasir",
"label": "string",
"currencies": [
"IDR"
],
"currency": "IDR"
}
]
}Price a checkout
/v1/billing/quotePrice a checkout without creating anything: the same body as POST /v1/billing/checkout. Use it to show the annual saving, check a coupon (400 COUPON_INVALID with reason: not_found, inactive, not_started, expired, used_up, already_used, wrong_plan, wrong_period, wrong_currency) and show the upgrade credit before paying. provider is the method that would be used (null when none is set up).
Request body
plan_idintegerPlan to buy; omit to renew the current planproviderstringcurrencystringISO 4217 code, e.g.USD,IDRbilling_periodstringDefaultmonthlycoupon_codestringCase-insensitive
Responses
- 200Quote
Response fields
plan_idintegerrequiredplan_namestringrequiredbilling_periodstringrequiredmonthsintegerrequiredMonths charged (12 months are delivered for an annual period)currencystringrequiredISO 4217 code, e.g.USD,IDRchangestringrequiredupgradestarts now with a credit;downgradestarts when the current period ends;renewstacks after itlist_amountintegerrequiredPlan price × months chargedmonthly_equivalentintegerrequiredWhat the same months would cost month by monthdiscount_amountintegerrequiredcoupon_codestring | nullrequiredproration_creditintegerrequiredUnused part of what was paid for the current plan (upgrades only)overageintegerrequiredamountintegerrequiredWhat will be charged, minor unitsstarts_atintegerrequiredends_atintegerrequiredproviderstring | nullrequired
- 400
VALIDATION_FAILED,COUPON_INVALID,CURRENCY_NOT_SUPPORTED, or nothing to pay
curl -X POST "https://api.chatkomodo.com/v1/billing/quote" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"plan_id": 2,
"provider": "creem",
"currency": "USD"
}'const res = await fetch('https://api.chatkomodo.com/v1/billing/quote', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"plan_id": 2,
"provider": "creem",
"currency": "USD"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/billing/quote",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"plan_id": 2,
"provider": "creem",
"currency": "USD",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/billing/quote', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'plan_id' => 2,
'provider' => 'creem',
'currency' => 'USD',
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"plan_id": 1,
"plan_name": "string",
"billing_period": "monthly",
"months": 1,
"currency": "IDR",
"change": "new",
"list_amount": 150000,
"monthly_equivalent": 1,
"discount_amount": 12,
"coupon_code": "482913",
"proration_credit": 1,
"overage": 1,
"amount": 150000,
"starts_at": 1767225600000,
"ends_at": 1767225600000,
"provider": "pakasir"
}Create an invoice and payment page
/v1/billing/checkoutCreate an invoice and a hosted payment page. Redirect the browser to pay_url; the provider sends the buyer back to /billing?invoice=<order_id> (or ?cancelled=<order_id>). Omit plan_id to pay the current plan plus any overage for the month; send plan_id to buy or switch plans. billing_period: "annual" buys 12 months for the price of 10 (set by the operator). A coupon_code takes a percent or a fixed amount off the plan price (never off overage). Switching to a pricier plan starts it now and credits the unused part of what was paid for the current plan; a cheaper plan starts when the current period ends. Price the same body first with POST /v1/billing/quote. provider and currency default to the best match for the tenant. The plan is activated only after the provider confirms the payment (webhook or /invoices/{order_id}/confirm).
Request body
plan_idintegerPlan to buy; omit to renew the current planproviderstringcurrencystringISO 4217 code, e.g.USD,IDRbilling_periodstringDefaultmonthlycoupon_codestringCase-insensitive
Responses
- 200Payment created. With
amount0 (a 100 % coupon, or an upgrade credit that covers everything) nothing is collected:statusispaid,pay_urlis null and the plan is already activeResponse fields
pay_urlstring (uri) | nullrequiredorder_idstringrequiredamountintegerrequiredMinor units ofcurrencycurrencystringrequiredISO 4217 code, e.g.USD,IDRproviderstringrequiredstatusstringrequiredoverageintegerrequiredPart ofamountthat is usage beyond the quota (minor units)quoteobjectrequiredquote.plan_idintegerrequiredquote.plan_namestringrequiredquote.billing_periodstringrequiredquote.monthsintegerrequiredMonths charged (12 months are delivered for an annual period)quote.currencystringrequiredISO 4217 code, e.g.USD,IDRquote.changestringrequiredupgradestarts now with a credit;downgradestarts when the current period ends;renewstacks after itquote.list_amountintegerrequiredPlan price × months chargedquote.monthly_equivalentintegerrequiredWhat the same months would cost month by monthquote.discount_amountintegerrequiredquote.coupon_codestring | nullrequiredquote.proration_creditintegerrequiredUnused part of what was paid for the current plan (upgrades only)quote.overageintegerrequiredquote.amountintegerrequiredWhat will be charged, minor unitsquote.starts_atintegerrequiredquote.ends_atintegerrequired
- 400
VALIDATION_FAILED;COUPON_INVALID(withreason);CURRENCY_NOT_SUPPORTEDwhen the plan has no price in a currency that provider accepts; or nothing to pay (free plan) - 502The payment service is unavailable; nothing was stored, retry shortly
- 503No payment provider is configured
curl -X POST "https://api.chatkomodo.com/v1/billing/checkout" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"plan_id": 2,
"provider": "creem",
"currency": "USD"
}'const res = await fetch('https://api.chatkomodo.com/v1/billing/checkout', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"plan_id": 2,
"provider": "creem",
"currency": "USD"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/billing/checkout",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"plan_id": 2,
"provider": "creem",
"currency": "USD",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/billing/checkout', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'plan_id' => 2,
'provider' => 'creem',
'currency' => 'USD',
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"pay_url": "https://example.com/file.jpg",
"order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"amount": 150000,
"currency": "IDR",
"provider": "pakasir",
"status": "pending",
"overage": 1,
"quote": {
"plan_id": 1,
"plan_name": "string",
"billing_period": "monthly",
"months": 1,
"currency": "IDR",
"change": "new",
"list_amount": 150000,
"monthly_equivalent": 1,
"discount_amount": 12,
"coupon_code": "482913",
"proration_credit": 1,
"overage": 1,
"amount": 150000,
"starts_at": 1767225600000,
"ends_at": 1767225600000
}
}List invoices
/v1/billing/invoicesThe 24 most recent invoices of the tenant.
Responses
- 200Invoices
Response fields
invoicesarray of objectrequiredinvoices[].order_idstringrequiredinvoices[].periodstringrequiredYYYY-MMinvoices[].plan_idinteger | nullrequiredinvoices[].amountintegerrequiredMinor units ofcurrencyinvoices[].currencystringrequiredISO 4217 code, e.g.USD,IDRinvoices[].providerstringrequirednone= nothing was collected (100 % coupon or credit)invoices[].billing_periodstringrequiredinvoices[].monthsintegerrequiredinvoices[].list_amountinteger | nullrequiredinvoices[].discount_amountintegerrequiredinvoices[].coupon_codestring | nullrequiredinvoices[].proration_creditintegerrequiredinvoices[].overage_amountintegerrequiredinvoices[].plan_changestring | nullrequiredinvoices[].statusstringrequiredpending,paid,expired,cancelledorfailedinvoices[].created_atintegerrequiredinvoices[].paid_atinteger | nullrequired
curl "https://api.chatkomodo.com/v1/billing/invoices" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/billing/invoices', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/billing/invoices",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/billing/invoices', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"invoices": [
{
"order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"period": "string",
"plan_id": 1,
"amount": 150000,
"currency": "IDR",
"provider": "pakasir",
"billing_period": "monthly",
"months": 1,
"list_amount": 150000,
"discount_amount": 12,
"coupon_code": "482913",
"proration_credit": 1,
"overage_amount": 150000,
"plan_change": "new",
"status": "active",
"created_at": 1767225600000,
"paid_at": 1767225600000
}
]
}Check the payment of an invoice
/v1/billing/invoices/{order_id}/confirmAsk the payment provider about one invoice (call it when the buyer returns from checkout, or when the webhook is late). Rate-limited to one real check per 4 seconds per invoice; faster calls answer pending with retry_after_s. A paid result activates the plan.
Path parameters
order_idstringrequired
Responses
- 200Current state
Response fields
statusstringrequiredrejected= the payment does not match the invoice (amount, order, test mode) and was not activated;unverified= it cannot be verified automatically, contact supportretry_after_snumberprovider_statusstringnotestring
- 404Invoice not found (
NOT_FOUND)
curl -X POST "https://api.chatkomodo.com/v1/billing/invoices/INV-20260101-0001/confirm" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/billing/invoices/INV-20260101-0001/confirm', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/billing/invoices/INV-20260101-0001/confirm",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/billing/invoices/INV-20260101-0001/confirm', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"status": "paid",
"retry_after_s": 0.5,
"provider_status": "active",
"note": "Prefers delivery after 5 pm"
}Get the billing email settings
/v1/billing/notificationsBilling email of the tenant: invoices, payment receipts, "plan ends in 3 days" and "plan ended" are emailed there (as well as the WhatsApp reminder), in the language of the business profile.
Responses
- 200Preferences
Response fields
emailstring | nullrequiredWhere invoices, receipts and plan reminders go; null = noneenabledbooleanrequiredavailablebooleanrequiredWhether the platform has email set up at all
curl "https://api.chatkomodo.com/v1/billing/notifications" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/billing/notifications', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/billing/notifications",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/billing/notifications', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"email": "[email protected]",
"enabled": true,
"available": true
}Update the billing email settings
/v1/billing/notificationsDashboard loginSet the billing email (null or empty removes it) and/or switch billing emails on or off. Owner login only (403 for agents and API keys). Audited as billing.notifications.
Request body
emailstring | nullenabledboolean
Responses
- 200Saved
Response fields
emailstring | nullrequiredWhere invoices, receipts and plan reminders go; null = noneenabledbooleanrequiredavailablebooleanrequiredWhether the platform has email set up at all
- 400
VALIDATION_FAILED - 403Not the owner
curl -X PUT "https://api.chatkomodo.com/v1/billing/notifications" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"enabled": true
}'const res = await fetch('https://api.chatkomodo.com/v1/billing/notifications', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"email": "[email protected]",
"enabled": true
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.put(
"https://api.chatkomodo.com/v1/billing/notifications",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"email": "[email protected]",
"enabled": True,
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('PUT', 'https://api.chatkomodo.com/v1/billing/notifications', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'email' => '[email protected]',
'enabled' => true,
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"email": "[email protected]",
"enabled": true,
"available": true
}