Billing
Billing profile, rates, status and usage.
Get billing details
Section titled “Get billing details”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
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 |
Set billing country
Section titled “Set billing country”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
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 configuration
Section titled “Get billing configuration”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
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
Section titled “Get billing status”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
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 usage summary
Section titled “Get usage summary”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
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 |
List usage records
Section titled “List usage records”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
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 |
List plans
Section titled “List plans”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
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 the rate card
Section titled “Get the rate card”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
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 the plan wallet balance
Section titled “Get the plan wallet balance”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
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 |
Quote a plan change
Section titled “Quote a plan change”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
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 |
List GST invoices
Section titled “List GST invoices”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
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 a GST invoice
Section titled “Get a GST invoice”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
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 |
Download a GST invoice PDF
Section titled “Download a GST invoice PDF”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
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 |
Start a plan subscription
Section titled “Start a plan subscription”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
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. |
Cancel a plan subscription
Section titled “Cancel a plan subscription”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
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. |
Change plan
Section titled “Change plan”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
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. |
Restart a plan after a failed renewal
Section titled “Restart a plan after a failed renewal”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
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. |

