Plan and add-ons
Read the project's email plan, renew or change it, and buy extra mailboxes and inbox storage.
Every project's email service runs on a plan (Free, Starter, Pro, Business). An API key can read the plan, renew it, change it, and set the inbox add-ons. Purchases are paid from your Helipod wallet, like in the dashboard.
Two rules for purchases with a key
- Every paid call accepts
?dryRun=true: nothing is charged, the response shows the price and your wallet after it. - The real call needs an
Idempotency-Keyheader (8 to 128 characters: letters, digits,_ . : -). A retry with the same value returns the first answer instead of charging again. Without it the call answers400 IDEMPOTENCY_KEY_REQUIRED.
Without an active plan (the Free plan counts), a key can still read the status and the plan, and buy or renew one; every other route answers 402 NO_PLAN until then. A Helipod API key or an AI tool signed in with the Email API can do this even for a project that has no email yet: buying a plan then also sets email up (see Buy a plan). Cancelling a plan, the auto-renew and pay-as-you-go switches and email API keys stay in the dashboard: those routes answer 403 KEY_NOT_ALLOWED to a key.
Plans
/planscurl https://mail.helipod.io/api/email/plansEach plan has a code (FREE, STARTER, PRO, BUSINESS), a monthly price in IDR, the discounts for longer cycles (discount3month, discount6month, yearlyDiscount, in percent; 0 means the cycle is not offered) and its limits.
Current plan
/plancurl https://mail.helipod.io/api/email/plan \
-H "Authorization: Bearer your_email_api_key"{
"success": true,
"data": {
"plan": { "code": "STARTER", "name": "Starter", "price": "29400.00", "limits": { "dailySendLimit": 0, "monthlySendLimit": 2500, "maxDomains": 2 } },
"subscription": {
"status": "active",
"startedAt": "2026-10-01T03:00:00.000Z",
"expiredAt": "2026-11-01T03:00:00.000Z",
"code": "STARTER",
"billingMonths": 1,
"extraMailboxes": 0,
"extraStorageGb": 0
}
}
}plan and subscription are null when the project has no active plan. The limits object is shortened here.
Renew
/plan/renewcharges your walletdryRunIdempotency-KeyProp
Type
curl -X POST "https://mail.helipod.io/api/email/plan/renew?dryRun=true" \
-H "Authorization: Bearer your_email_api_key" \
-H "Content-Type: application/json" \
-d '{ "months": 3 }'{
"success": true,
"data": {
"dryRun": true,
"action": "renew",
"mode": "extend",
"planCode": "STARTER",
"months": 3,
"charge": "83790.00",
"expiredAt": "2027-02-01T03:00:00.000Z",
"currency": "IDR",
"walletBalance": "500000.00",
"walletBalanceAfter": "416210.00",
"enoughBalance": true
}
}The new cycle starts when the current one ends; expiredAt is the new end date. Then send the same request without dryRun and with an Idempotency-Key:
curl -X POST https://mail.helipod.io/api/email/plan/renew \
-H "Authorization: Bearer your_email_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: email-renew-2026-10-a1b2" \
-d '{ "months": 3 }'The response is { "activated": true, "charged": "83790.00", "extended": true, "subscription": { ... } } with the new expiredAt in subscription. Renewing into another plan answers 409 PLAN_ACTIVE: use change instead.
Change plan
/plan/changecharges your walletdryRunIdempotency-KeyProp
Type
- A larger plan applies at once. You pay the difference for the rest of the current cycle; the end date stays the same. The dry run answers
mode: "immediate"with thecharge. - A smaller plan is scheduled for the next cycle and costs nothing now. The dry run answers
mode: "scheduled"witheffectiveAt. - The plan must fit what the project already uses, for example its number of domains.
A paid change answers 200, a scheduled one 201.
Buy a plan
/plancharges your walletdryRunIdempotency-KeyProp
Type
While a plan is active, buying the same plan changes nothing (mode: "already_active" in a dry run) and buying another one answers 409 PLAN_ACTIVE or 409 DOWNGRADE_NOT_ALLOWED: use change. A paid purchase answers 200, a free activation 201.
With a Helipod API key (or an AI tool signed in) for a project that has no email yet, the purchase also sets email up for it, and the response adds setup: { configSetId, alreadyActive, jobId }. Setup finishes in the background; then add a sending domain. If setup fails, the plan stays bought and setup holds { code, error }: retry with POST /setup.
Inbox add-ons
/plan/addonscharges your walletdryRunIdempotency-KeyProp
Type
curl -X POST "https://mail.helipod.io/api/email/plan/addons?dryRun=true" \
-H "Authorization: Bearer your_email_api_key" \
-H "Content-Type: application/json" \
-d '{ "extraMailboxes": 2, "extraStorageGb": 5 }'{
"success": true,
"data": {
"extraMailboxes": 2,
"extraStorageGb": 5,
"charge": "18000.00",
"remainingDays": 15,
"monthlyAfter": "36000.00",
"expiredAt": "2026-11-01T03:00:00.000Z",
"dryRun": true,
"currency": "IDR",
"walletBalance": "500000.00",
"walletBalanceAfter": "482000.00",
"enoughBalance": true
}
}- An increase is charged now, pro rata to the end of the current cycle (
charge), and then renews with the plan (monthlyAfterper month). - A decrease costs nothing and is refused while you use more than is left (
409 ADDON_IN_USE): delete a mailbox or some mail first. - Add-ons need a plan that includes receiving (
402 PLAN_REQUIREDon the Free plan).
Errors
| Code | Status | Meaning |
|---|---|---|
IDEMPOTENCY_KEY_REQUIRED | 400 | A purchase with a key was sent without an Idempotency-Key header. |
REQUEST_IN_PROGRESS | 409 | A request with the same Idempotency-Key is still running. |
PLAN_REQUIRED | 400 / 402 | planCode is missing (400), or add-ons on a plan without receiving (402). |
PLAN_NOT_FOUND | 404 | No plan with this code. |
NO_PLAN | 404 | No plan to renew or change. |
PLAN_ACTIVE | 409 | Another plan is active: use change. |
DOWNGRADE_NOT_ALLOWED | 409 | A smaller plan can't be bought while a larger one is active: use change to schedule it. |
ADDON_IN_USE | 409 | The add-ons can't go below what is in use. |
INSUFFICIENT_BALANCE | 402 | The wallet can't cover the charge. Nothing is charged. |
KEY_NOT_ALLOWED | 403 | The route stays in the dashboard. |