Skip to content

Billing

Billing profile, rates, status and usage.

GET /billing/details/{organisationId}

Returns the organisation’s billing identity: country, company, address, GSTIN and billing mobile. Any member role may read it. The organisationId must be the key’s own organisation.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId path string yes Your organisation ID. Must be the API key’s own organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/details/{organisationId}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"billingCountry": "IN",
"companyName": "Sharma Foods Pvt Ltd",
"companyRegistrationNumber": null,
"addressLine1": "12 MG Road",
"addressLine2": null,
"city": "Bengaluru",
"state": "Karnataka",
"postalCode": "560001",
"gstin": "29ABCDE1234F1Z5",
"mobile": "+919876543210"
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, foreign organisation, or the key’s owner is not a member
404 Organisation not found
429 Rate limited

PUT /billing/country/{organisationId}

Sets the country the organisation is billed from, which decides the checkout currency, the payment processor and tax treatment (India: rupees with GST; elsewhere: US dollars). Required before the first top-up or plan. The key’s owner must be OWNER or ADMIN. Changing to a different country expires every pending (unpaid) pay-as-you-go checkout; setting the same country again is a no-op. For an organisation on plan billing, setting the country for the first time adds the one-time sign-up credit (Rs 100 or USD 1) to the plan wallet, and moving between India and another country is refused (409) while the plan wallet holds money or a subscription exists. The response is the pay-as-you-go billing status (the same shape as GET /billing/status without billingModel).

Scope: wallet:write

Parameters

Name In Type Required Description
organisationId path string yes Your organisation ID. Must be the API key’s own organisation.

Body (application/json)

Field Type Required Description
billingCountry string yes ISO 3166-1 alpha-2 code from the supported list (about 90 countries, for example IN, US, GB, SG, AE). Case-insensitive.

Example request

Terminal window
curl -X PUT "https://api.foan.ai/api/v1/billing/country/{organisationId}" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"billingCountry":"IN"}'

Response 200

{
"success": true,
"message": "Billing country updated",
"data": {
"canMakeCalls": true,
"canSendMessage": true,
"isActive": true,
"isSuspended": false,
"balance": 42.5,
"isLowBalance": false,
"ratePerMinute": 0.06,
"ratePerMessage": 0.0015,
"currency": "USD",
"billingCountry": "IN",
"displayCurrency": "INR",
"fxRate": 88.12,
"fxSource": "live",
"checkoutCurrency": "INR",
"hasPaid": true,
"minTopUpAmount": 50,
"topUpQuickAmounts": [
50,
100,
250
]
}
}

Errors

Code When
400 billingCountry missing or not a country we can bill in
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing wallet:write scope, foreign organisation, or the key’s owner is not OWNER/ADMIN
404 Organisation not found
409 Plan billing only: the change would switch the wallet currency (India to elsewhere or back) while the plan wallet holds money or a subscription exists; contact support
429 Rate limited

GET /billing/config/{organisationId}

Returns the organisation’s rates and top-up settings, all in USD. Any member role may read it. The organisationId must be the key’s own organisation.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId path string yes Your organisation ID. Must be the API key’s own organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/config/{organisationId}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"ratePerMinute": 0.06,
"ratePerMessage": 0.0015,
"lowBalanceThreshold": 0.1,
"minTopUpAmount": 50,
"topUpQuickAmounts": [
50,
100,
250
],
"isActive": true
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, foreign organisation, or the key’s owner is not a member
404 Billing not configured for this organisation
429 Rate limited

GET /billing/status/{organisationId}

Tells you whether the organisation can place calls and send messages right now, with its billing model, its pay-as-you-go USD balance and rates, and the local display currency and exchange rate. For an organisation on plan billing (billingModel WALLET) canMakeCalls and canSendMessage come from the plan wallet (money in it, plan not frozen), while balance, isLowBalance, ratePerMinute and ratePerMessage are still the old pay-as-you-go USD figures and do not describe the plan wallet; read GET /billing/balance and GET /billing/rate-card for those. An organisation whose AI calls are paid from a Muncho restaurant wallet gets that wallet instead (walletProvider muncho, rupees, plus callRefusalReason). Any member role may read it. The organisationId must be the key’s own organisation.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId path string yes Your organisation ID. Must be the API key’s own organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/status/{organisationId}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"canMakeCalls": true,
"canSendMessage": true,
"isActive": true,
"isSuspended": false,
"balance": 42.5,
"isLowBalance": false,
"ratePerMinute": 0.06,
"ratePerMessage": 0.0015,
"currency": "USD",
"billingCountry": "IN",
"displayCurrency": "INR",
"fxRate": 88.12,
"fxSource": "live",
"checkoutCurrency": "INR",
"hasPaid": true,
"minTopUpAmount": 50,
"topUpQuickAmounts": [
50,
100,
250
],
"billingModel": "LEGACY_USD"
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, foreign organisation, or the key’s owner is not a member
429 Rate limited
503 Only for an organisation whose AI calls are paid from a Muncho restaurant wallet: that wallet could not be reached, or is being switched over; retry in a minute

GET /billing/usage/{organisationId}

Totals the organisation’s billed usage (duration and USD amount), overall and per usage type. All-time unless both startDate and endDate are given. Any member role may read it. For an organisation on plan billing the money actually comes out of the plan wallet in its own currency (see GET /wallet/{organisationId}/transactions); the USD amounts here are that charge converted at the display exchange rate when the call was billed.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId path string yes Your organisation ID. Must be the API key’s own organisation.
startDate query string no Start of the period (inclusive, on call start time). Only applied when endDate is also sent.
endDate query string no End of the period (inclusive). Only applied when startDate is also sent.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/usage/{organisationId}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"periodStart": "2026-09-01T00:00:00.000Z",
"periodEnd": "2026-09-30T23:59:59.000Z",
"totalDurationSeconds": 45210,
"totalDurationMinutes": 753.5,
"totalAmount": 45.21,
"totalCalls": 812,
"currency": "USD",
"byUsageType": {
"VOICE_CALL": {
"count": 640,
"durationSeconds": 45210,
"amount": 45.21
},
"CHATBOT_MESSAGE": {
"count": 172,
"durationSeconds": 0,
"amount": 0.26
}
}
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, foreign organisation, or the key’s owner is not a member
429 Rate limited
500 Unparseable date values are not validated and fail as a server error

GET /billing/usage/{organisationId}/details

Lists individual usage records (calls, messages, number rental) newest first, with filters and pagination. Amounts are USD. Any member role may read it. For an organisation on plan billing, totalAmount is the plan wallet charge converted to USD at the display exchange rate when the call was billed, and ratePerMinute is the old pay-as-you-go rate, not the plan price.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId path string yes Your organisation ID. Must be the API key’s own organisation.
startDate query string no Only records starting at or after this time.
endDate query string no Only records starting at or before this time.
usageType query string: VOICE_CALL, RECORDING, SMS, CREDIT, CHATBOT_MESSAGE, NUMBER_RENTAL no Filter by usage type.
billed query string: true, false no “true” for billed records only; any other value means unbilled only.
page query integer no Page number.
limit query integer no Page size (clamped to 1..100).

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/usage/{organisationId}/details" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"records": [
{
"id": "cmf3k2j9x0010abcd1234efgh",
"organisationId": "cmf3k2j9x0001abcd1234efgh",
"voiceSessionId": "66f1c2e8a4b5c6d7e8f90123",
"roomName": "call-7f3a9c2e",
"callDirection": "OUTBOUND",
"usageType": "VOICE_CALL",
"durationSeconds": 95,
"billedSeconds": 95,
"messageCount": 0,
"ratePerMinute": 0.06,
"totalAmount": 0.095,
"startTime": "2026-09-20T09:00:00.000Z",
"endTime": "2026-09-20T09:01:35.000Z",
"billed": true,
"walletDeducted": true,
"createdAt": "2026-09-20T09:01:36.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, foreign organisation, or the key’s owner is not a member
429 Rate limited
500 An invalid usageType or unparseable date is not validated and fails as a server error

GET /billing/plans

Lists the plans this organisation can buy, priced in its own billing currency: rupees (INR, amounts in paise) for an organisation billed from India, US dollars (USD, amounts in cents) everywhere else. The billing country decides the currency and the payment processor, so it must be set first (PUT /billing/country). Every plan puts a fixed amount of money (allowanceMinor) in the plan wallet every 30 days, the same on monthly and yearly billing. canSubscribe is false for an organisation still on the earlier pay-as-you-go USD billing; it can read plans but not buy one. Any member role may read it. Read only, nothing is charged.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId query string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/plans" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"organisationId": "cmf3k2j9x0001abcd1234efgh",
"billingCountry": "IN",
"currency": "INR",
"rail": "RAZORPAY",
"product": "FOAN",
"billingModel": "WALLET",
"canSubscribe": true,
"plans": [
{
"planId": "cmg1pln0000starmoninr0001",
"code": "foan_starter_monthly_inr",
"tier": "STARTER",
"interval": "MONTHLY",
"currency": "INR",
"feeMinor": 239900,
"perMonthMinor": 239900,
"chargeMinor": 283082,
"taxMinor": 43182,
"taxInclusive": true,
"allowanceMinor": 200000,
"perMinuteMinor": 600,
"minutesIncluded": 333,
"concurrentCalls": 5,
"topUpPresets": [
100000,
500000,
2000000
],
"rail": "RAZORPAY",
"collectionMode": "MANDATE"
},
{
"planId": "cmg1pln0000promoninr00002",
"code": "foan_pro_monthly_inr",
"tier": "PRO",
"interval": "MONTHLY",
"currency": "INR",
"feeMinor": 599900,
"perMonthMinor": 599900,
"chargeMinor": 707882,
"taxMinor": 107982,
"taxInclusive": true,
"allowanceMinor": 500000,
"perMinuteMinor": 500,
"minutesIncluded": 1000,
"concurrentCalls": 10,
"topUpPresets": [
100000,
500000,
2000000
],
"rail": "RAZORPAY",
"collectionMode": "MANDATE"
}
]
}
}

Errors

Code When
400 The organisation has no billing country yet
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, or organisationId is not the key’s own organisation
404 Organisation not found
429 Rate limited

GET /billing/rate-card

Returns what each metered thing (an AI call minute, a chat reply, a phone line minute, a number’s monthly rental) costs this organisation, in the currency of its plan wallet: INR in paise for India, USD in cents elsewhere. Prices follow the tier of the organisation’s paid plan; with no paid plan they are Starter prices. Lines not on sale yet are left out. An organisation with no billing country is shown USD prices. Any member role may read it. Read only.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId query string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.
at query string no Read the prices in force at this moment instead of now. Any value Date can parse; otherwise 400.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/rate-card" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"organisationId": "cmf3k2j9x0001abcd1234efgh",
"product": "FOAN",
"currency": "INR",
"tier": "PRO",
"effectiveAt": "2026-10-05T09:00:00.000Z",
"lines": [
{
"key": "ai_phone_call_minute",
"label": "AI phone call",
"unit": "MINUTE",
"priceMinor": 500,
"currency": "INR",
"tier": "PRO",
"pulseSeconds": 30,
"minimumSeconds": 30,
"managedOnly": false,
"effectiveFrom": "2026-09-01T00:00:00.000Z"
},
{
"key": "ai_web_chat_reply",
"label": "AI website chat reply",
"unit": "REPLY",
"priceMinor": 50,
"currency": "INR",
"tier": "PRO",
"pulseSeconds": null,
"minimumSeconds": null,
"managedOnly": false,
"effectiveFrom": "2026-09-01T00:00:00.000Z"
},
{
"key": "number_rental_month",
"label": "Phone number rental",
"unit": "MONTH",
"priceMinor": 50000,
"currency": "INR",
"tier": "ALL",
"pulseSeconds": null,
"minimumSeconds": null,
"managedOnly": true,
"effectiveFrom": "2026-09-01T00:00:00.000Z"
},
{
"key": "phone_line_minute",
"label": "Phone line",
"unit": "MINUTE",
"priceMinor": 50,
"currency": "INR",
"tier": "ALL",
"pulseSeconds": 30,
"minimumSeconds": 30,
"managedOnly": true,
"effectiveFrom": "2026-09-01T00:00:00.000Z"
}
]
}
}

Errors

Code When
400 at is not a valid date
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, or organisationId is not the key’s own organisation
404 Organisation not found
429 Rate limited

GET /billing/balance

Returns the plan wallet: spendable money, how much of it came from the plan allowance and how much was bought as top-ups, each live grant with its expiry, the subscription and its next refresh, and any checkout started but not yet paid. All amounts are minor units of currency (paise for INR, cents for USD) and may carry up to 4 decimals. This is the balance for plan billing; an organisation still on the earlier pay-as-you-go USD billing (billingModel LEGACY_USD) should read GET /wallet/{organisationId} instead. Any member role may read it. Read only.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId query string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/balance" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"organisationId": "cmf3k2j9x0001abcd1234efgh",
"billingModel": "WALLET",
"balanceMinor": 412350.5,
"currency": "INR",
"planMinor": 312350.5,
"topUpMinor": 100000,
"grants": [
{
"id": "cmg2grt0000a1b2c3d4e5f6g7",
"source": "PLAN_CYCLE",
"amountMinor": 500000,
"remainingMinor": 312350.5,
"expiresAt": "2026-10-31T06:29:41.000Z"
},
{
"id": "cmg2grt0001a1b2c3d4e5f6g7",
"source": "TOP_UP",
"amountMinor": 100000,
"remainingMinor": 100000,
"expiresAt": "2027-10-02T10:00:00.000Z"
}
],
"nextRefreshAt": "2026-10-31T06:29:41.000Z",
"subscription": {
"id": "cmg2sub0000a1b2c3d4e5f6g7",
"status": "ACTIVE",
"planId": "cmg1pln0000promoninr00002",
"planCode": "foan_pro_monthly_inr",
"tier": "PRO",
"interval": "MONTHLY",
"currency": "INR",
"allowanceMinor": 500000,
"currentPeriodEnd": "2026-10-31T06:29:41.000Z",
"nextGrantAt": "2026-10-31T06:29:41.000Z",
"cancelAtPeriodEnd": false,
"graceUntil": null,
"checkoutUrl": null,
"scheduledChange": null,
"renewalNeedsApproval": false,
"canRestart": false
},
"pendingCheckout": null
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, or organisationId is not the key’s own organisation
404 Organisation not found
429 Rate limited

GET /billing/subscriptions/{id}/change-quote

Says what moving this subscription to another plan would cost today, with the same checks POST /billing/subscriptions/{id}/change makes, and creates nothing. India: an upgrade pays the difference for the rest of the current period now (dueNow, paise, GST included) and the new plan starts at the end of the period; a downgrade costs nothing now and starts at the end of the period. Outside India dueNow is always null: an upgrade takes effect immediately and the processor works out the prorated charge, a downgrade starts at renewal. Any member role may read it. Read only.

Scope: billing:read

Parameters

Name In Type Required Description
id path string yes The Foan subscription ID (from GET /billing/balance, subscription.id or pendingCheckout.subscriptionId).
organisationId query string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.
planId query string yes The plan to move to (from GET /billing/plans).

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/subscriptions/{id}/change-quote" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"subscriptionId": "cmg2sub0000a1b2c3d4e5f6g7",
"rail": "RAZORPAY",
"fromPlanId": "cmg1pln0000starmoninr0001",
"toPlanId": "cmg1pln0000promoninr00002",
"move": "upgrade",
"effectiveAt": "next_billing_date",
"startsAt": "2026-10-31T06:29:41.000Z",
"dueNow": {
"totalMinor": 339840,
"netMinor": 288000,
"taxMinor": 51840,
"currency": "INR"
}
}
}

Errors

Code When
400 planId missing, the plan is no longer on sale, the plan is priced in another currency, or the subscription is on another payment route
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, or organisationId is not the key’s own organisation
404 Subscription not found (or not this organisation’s), plan not found, or organisation not found
409 The subscription has already ended, it is already on this plan, the billing country no longer matches the plan, or (India) a yearly plan upgrade, which our team does by hand
429 Rate limited

GET /billing/invoices

Lists the organisation’s GST tax invoices and credit notes, newest first. Invoices are issued only for rupee payments on plan billing in India (plan charges, top-ups and upgrade differences), so an organisation billed elsewhere gets an empty list. All amounts are paise. Any member role may read it.

Scope: billing:read

Parameters

Name In Type Required Description
organisationId query string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.
limit query integer no Maximum documents to return (clamped to 1..200). No paging beyond this.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/invoices" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"organisationId": "cmf3k2j9x0001abcd1234efgh",
"invoices": [
{
"id": "cmg2inv0000a1b2c3d4e5f6g7",
"kind": "INVOICE",
"number": "FOAN/26-27/00042",
"issuedAt": "2026-10-01T06:30:00.000Z",
"paidAt": "2026-10-01T06:29:41.000Z",
"purpose": "plan_charge",
"description": "Foan Pro plan, monthly",
"currency": "INR",
"taxableMinor": 599900,
"cgstMinor": 53991,
"sgstMinor": 53991,
"igstMinor": 0,
"totalMinor": 707882,
"placeOfSupply": "Karnataka (29)",
"buyerGstin": "29ABCDE1234F1Z5",
"periodStart": "2026-10-01T06:29:41.000Z",
"periodEnd": "2026-10-31T06:29:41.000Z",
"originalInvoiceId": null
}
]
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, or organisationId is not the key’s own organisation
429 Rate limited

GET /billing/invoices/{invoiceId}

Returns one GST invoice or credit note of the organisation. With include=pdf the PDF is also returned, base64 encoded. Amounts are paise. Any member role may read it.

Scope: billing:read

Parameters

Name In Type Required Description
invoiceId path string yes Foan invoice ID from GET /billing/invoices.
organisationId query string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.
include query string: pdf no Send pdf to also get filename and pdfBase64.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/invoices/{invoiceId}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"invoice": {
"id": "cmg2inv0000a1b2c3d4e5f6g7",
"kind": "INVOICE",
"number": "FOAN/26-27/00042",
"issuedAt": "2026-10-01T06:30:00.000Z",
"paidAt": "2026-10-01T06:29:41.000Z",
"purpose": "plan_charge",
"description": "Foan Pro plan, monthly",
"currency": "INR",
"taxableMinor": 599900,
"cgstMinor": 53991,
"sgstMinor": 53991,
"igstMinor": 0,
"totalMinor": 707882,
"placeOfSupply": "Karnataka (29)",
"buyerGstin": "29ABCDE1234F1Z5",
"periodStart": "2026-10-01T06:29:41.000Z",
"periodEnd": "2026-10-31T06:29:41.000Z",
"originalInvoiceId": null
}
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, or organisationId is not the key’s own organisation
404 Invoice not found, or it belongs to another organisation
429 Rate limited

GET /billing/invoices/{invoiceId}/pdf

Downloads one GST invoice or credit note as a PDF attachment (filename is the document number with slashes turned into dashes, for example FOAN-26-27-00042.pdf). Not cached. Any member role may call it.

Scope: billing:read

Parameters

Name In Type Required Description
invoiceId path string yes Foan invoice ID from GET /billing/invoices.
organisationId query string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/billing/invoices/{invoiceId}/pdf" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing billing:read scope, or organisationId is not the key’s own organisation
404 Invoice not found, or it belongs to another organisation
429 Rate limited

POST /billing/subscriptions

Starts a subscription to a plan and returns a checkout link for a person to approve the recurring payment. Nothing is charged by this call; the payer is charged when they approve, and the plan allowance is added to the wallet when that payment reaches us. India (billing country IN): rupees, 18% GST included in chargeMinor (paise), an autopay mandate, and the organisation’s invoice details (company name, address, state, 6-digit PIN code, optional valid GSTIN) must be on file first, because they go on the GST invoice; they are set on the dashboard Billing page, which an API key cannot do. Everywhere else: US dollars (chargeMinor in cents, before any local sales tax the processor adds), possibly charged in the payer’s local currency. One subscription per organisation: an existing one, including an unpaid checkout, must be cancelled first. Only organisations on plan billing (billingModel WALLET) may subscribe. The key’s owner must be OWNER or ADMIN.

Scope: wallet:write

Body (application/json)

Field Type Required Description
organisationId string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.
planId string yes A planId from GET /billing/plans, in the organisation’s currency.
returnUrl string no Outside India only. Where the payer is sent after checkout. Must be on the Foan dashboard; any other address is replaced with the dashboard Billing page.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/billing/subscriptions" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"planId":"cmg1pln0000promoninr00002"}'

Response 201

{
"success": true,
"data": {
"subscriptionId": "cmg2sub0000a1b2c3d4e5f6g7",
"rail": "RAZORPAY",
"collectionMode": "MANDATE",
"checkoutUrl": "https://rzp.io/rzp/AbCdEf12",
"razorpayCheckout": {
"keyId": "<publishable checkout key>",
"subscriptionId": "sub_Pq8xY2abcDEF12"
},
"planId": "cmg1pln0000promoninr00002",
"planCode": "foan_pro_monthly_inr",
"currency": "INR",
"chargeMinor": 707882,
"allowanceMinor": 500000
}
}

Errors

Code When
400 planId missing, no billing country, the plan is no longer on sale or is priced in another currency, the billing country is not one we can bill in, or the processor rejected the details (outside India)
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing wallet:write scope, organisationId is not the key’s own organisation, or the key’s owner is not OWNER/ADMIN
404 Organisation or plan not found
409 The organisation is on the earlier pay-as-you-go billing, already has a subscription (or one that runs until its period ends), or (India) its invoice details are missing or invalid
429 Rate limited
500 Payments are not configured on this server
502 Our payment processor refused the request or did not respond. Nothing was charged.

POST /billing/subscriptions/{id}/cancel

Cancels the subscription at the end of the paid period (default): it is not renewed, and the plan keeps working until currentPeriodEnd. With immediate true it ends now: renewals stop at once, plan money stops at creditsEndAt, and nothing is refunded. Bought top-up money is kept either way. The key’s owner must be OWNER or ADMIN.

Scope: wallet:write

Parameters

Name In Type Required Description
id path string yes The Foan subscription ID (from GET /billing/balance, subscription.id or pendingCheckout.subscriptionId).
immediate query string: true no Same as immediate true in the body.

Body (application/json)

Field Type Required Description
organisationId string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.
immediate boolean no true ends the subscription now instead of at the end of the paid period.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/billing/subscriptions/{id}/cancel" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"immediate":false}'

Response 200

{
"success": true,
"data": {
"subscriptionId": "cmg2sub0000a1b2c3d4e5f6g7",
"rail": "RAZORPAY",
"status": "ACTIVE",
"cancelAtPeriodEnd": true,
"creditsEndAt": "2026-10-31T06:29:41.000Z"
}
}

Errors

Code When
400 The processor rejected the request (outside India)
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing wallet:write scope, organisationId is not the key’s own organisation, or the key’s owner is not OWNER/ADMIN
404 Subscription not found, or it belongs to another organisation
409 The subscription has already ended, or (outside India) it was never approved by the payer
429 Rate limited
502 Our payment processor refused the request or did not respond. Nothing was charged.

POST /billing/subscriptions/{id}/change

Moves the subscription to another plan. Check the price first with GET /billing/subscriptions/{id}/change-quote. India: the current subscription is set to end at its period end straight away, and a new subscription for the new plan starting then is created; a person must approve it at checkoutUrl or the plan simply ends at period end. For an upgrade the rest-of-period difference (paise, GST included) is also raised now as a separate payment link (upgradeDelta) unless immediate is false; it is added to the wallet when paid and the link expires within 7 days or at period end. Yearly plan upgrades are done by our team (409). Outside India: an upgrade takes effect immediately and the processor charges a prorated amount to the card on file (checkoutUrl carries a payment link only if the processor returns one); a downgrade takes effect at renewal; switching between monthly and yearly, and upgrading a yearly plan, are done by our team (409). The key’s owner must be OWNER or ADMIN.

Scope: wallet:write

Parameters

Name In Type Required Description
id path string yes The Foan subscription ID (from GET /billing/balance, subscription.id or pendingCheckout.subscriptionId).
immediate query string: true, false no Used only when the body has no boolean immediate.

Body (application/json)

Field Type Required Description
organisationId string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.
planId string yes The plan to move to, from GET /billing/plans, in the same currency.
immediate boolean no India only. Defaults to true, which raises the upgrade difference as a payment link now. false raises no link and the new plan simply starts at period end. Ignored outside India.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/billing/subscriptions/{id}/change" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"planId":"cmg1pln0000promoninr00002"}'

Response 200

{
"success": true,
"data": {
"subscriptionId": "cmg2sub0000a1b2c3d4e5f6g7",
"rail": "RAZORPAY",
"fromPlanId": "cmg1pln0000starmoninr0001",
"toPlanId": "cmg1pln0000promoninr00002",
"effectiveAt": "next_billing_date",
"newSubscriptionId": "cmg2sub0001a1b2c3d4e5f6g7",
"checkoutUrl": "https://rzp.io/rzp/GhIjKl34",
"razorpayCheckout": {
"keyId": "<publishable checkout key>",
"subscriptionId": "sub_Rs9zA3bcdEFG34"
},
"upgradeDelta": {
"checkoutUrl": "https://rzp.io/rzp/MnOpQr56",
"amountMinor": 339840,
"netMinor": 288000
}
}
}

Errors

Code When
400 planId missing, the plan is no longer on sale or is in another currency, or the processor rejected the change (outside India)
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing wallet:write scope, organisationId is not the key’s own organisation, or the key’s owner is not OWNER/ADMIN
404 Subscription not found (or not this organisation’s), plan not found, or organisation not found
409 Already on this plan, the subscription has ended or was never approved, the billing country no longer matches the plan, a yearly upgrade or monthly/yearly switch (done by our team), an organisation a previous change is still waiting for approval (India), or (India) invoice details are missing or invalid
429 Rate limited
502 Our payment processor refused the request or did not respond. Nothing was charged.

POST /billing/subscriptions/{id}/restart

India only. When a renewal has failed (subscription PAST_DUE or SUSPENDED, see canRestart on GET /billing/balance), stops the failed mandate and starts the same plan again from today as a new subscription. A person must approve it at checkoutUrl; approving takes the first payment (chargeMinor, paise, GST included) at once, and after that it renews on its own. Until it is paid the old subscription keeps its status, so a frozen wallet stays frozen. Calling again while the new one waits returns the same one. Outside India the processor retries itself and this returns 409. The key’s owner must be OWNER or ADMIN.

Scope: wallet:write

Parameters

Name In Type Required Description
id path string yes The Foan subscription ID (from GET /billing/balance, subscription.id or pendingCheckout.subscriptionId).

Body (application/json)

Field Type Required Description
organisationId string no Optional. Defaults to the API key’s own organisation; if sent it must be that organisation, otherwise 403.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/billing/subscriptions/{id}/restart" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'

Response 200

{
"success": true,
"data": {
"subscriptionId": "cmg2sub0000a1b2c3d4e5f6g7",
"rail": "RAZORPAY",
"newSubscriptionId": "cmg2sub0002a1b2c3d4e5f6g7",
"checkoutUrl": "https://rzp.io/rzp/StUvWx78",
"razorpayCheckout": {
"keyId": "<publishable checkout key>",
"subscriptionId": "sub_Tu1bC4defGHI56"
},
"chargeMinor": 707882,
"currency": "INR"
}
}

Errors

Code When
401 Missing, invalid or revoked API key, or the key’s owner is deactivated or no longer in the organisation
403 Missing wallet:write scope, organisationId is not the key’s own organisation, or the key’s owner is not OWNER/ADMIN
404 Subscription not found, or it belongs to another organisation
409 Not an Indian plan, the subscription has ended, there is no failed payment to make up, the plan is set to end, or invoice details are missing or invalid
429 Rate limited
502 Our payment processor refused the request or did not respond. Nothing was charged.