Skip to content
On this site

Billing

The team's plan, usage, invoices and add-ons, and the two hosted pages where a customer pays.

Who can call it

The two reads and the six writes are reached differently. A scope can read billing; nothing but a full_access key can change it.

What each credential can reach on the billing routes
CredentialThe two readsThe six writes
full_access200200
sending_access401 restricted_api_key401 restricted_api_key
OAuth token with billing:read200403 invalid_permission
OAuth token with every scope200403 invalid_permission

The reads are GET /billing and GET /billing/invoices. No OAuth scope reaches a billing write: a token holding every scope in the catalogue is still 403 invalid_permission on checkout, the portal, a plan change, pay-as-you-go and both add-on routes.

Hosted pages

  • POST /billing/checkout and POST /billing/portal answer a URL. Send the customer's browser there: the card is entered on that hosted page and never passes through this API.
  • return_url must be an absolute https URL. The browser comes back to it with checkout=success or checkout=cancelled appended; without one it comes back to the dashboard.
  • A checkout URL stops working at its expires_at. Ask for a new one.

A few changes are refused for a key even when it is full_access: anything that would turn off the team's single sign-on — removing the sso add-on, or a plan change that drops it — needs a signed-in admin, because it decides who may sign in.

Endpoints

GET /billing

The plan, this period's usage, payment state, add-ons and any pending change.

curl -X GET "https://api.rasket.com/billing" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "billing",
  "plan": {
    "code": "pro",
    "name": "Pro",
    "monthly_price_cents": 2000,
    "included_emails": 50000,
    "overage_per_1000_cents": 60
  },
  "effective_plan_code": "pro",
  "subscription": {
    "status": "active",
    "current_period_start": "2026-09-01T00:00:00.000Z",
    "current_period_end": "2026-10-01T00:00:00.000Z",
    "invoicing": "automatic",
    "latest_invoice_status": "paid"
  },
  "usage": {
    "period_start": "2026-09-01T00:00:00.000Z",
    "period_end": "2026-10-01T00:00:00.000Z",
    "emails": 12840,
    "included_emails": 50000
  },
  "pay_as_you_go": {
    "enabled": false
  },
  "payment_method": {
    "present": true,
    "brand": "visa",
    "last4": "4242"
  },
  "addons": [],
  "pending_change": null,
  "checkout_available": false
}
  • payment_method says whether a card is on file and shows its brand and last four digits. The card itself never leaves the payment provider.
  • checkout_available is true when the team has no paid subscription yet, so POST /billing/checkout is the way to start one.

GET /billing/invoices

The team's invoices, newest first, with links to the hosted invoice and its PDF.

curl -X GET "https://api.rasket.com/billing/invoices" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "in_1Q2w3E4r5T6y7U8i",
      "number": "ACME-0007",
      "status": "paid",
      "total_cents": 2000,
      "currency": "usd",
      "created_at": "2026-09-01T00:00:00.000Z",
      "hosted_invoice_url": "https://invoices.example.com/in_1Q2w3E4r5T6y7U8i",
      "invoice_pdf": "https://invoices.example.com/in_1Q2w3E4r5T6y7U8i.pdf"
    }
  ]
}
  • Read from the payment provider and cached for sixty seconds per team.

POST /billing/checkout

A hosted checkout page for a plan. Send the customer's browser to its `url`.

Body

Body
FieldTypeDescription
plan_code*stringThe plan to move to, such as pro or scale.
return_urlstringWhere the browser comes back to, with checkout=success or checkout=cancelled appended. Absolute https only. Defaults to the dashboard's plan page.
curl -X POST "https://api.rasket.com/billing/checkout" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "plan_code": "pro",
  "return_url": "https://app.example.com/settings/billing"
}'

Response 200

{
  "object": "checkout_session",
  "url": "https://checkout.example.com/c/pay/cs_live_a1B2c3D4",
  "expires_at": "2026-09-13T10:00:00.000Z"
}
  • The customer enters their card on the hosted page. Card details never pass through this API.
  • A return_url that is not absolute https is 422 validation_error.
  • Called with an API key, no email address is sent to the payment provider: the customer types one on the hosted page.

POST /billing/portal

A hosted page for the payment method, address, tax ID, invoices and cancellation.

curl -X POST "https://api.rasket.com/billing/portal" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "portal_session",
  "url": "https://billing.example.com/p/session/bps_a1B2c3D4"
}
  • Takes no body. Send the customer's browser to url.

POST /billing/plan

Move an existing subscription to another paid plan.

Body

Body
FieldTypeDescription
plan_code*stringThe plan to move to, such as pro or scale.
curl -X POST "https://api.rasket.com/billing/plan" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "plan_code": "scale"
}'

Response 200

{
  "object": "billing",
  "plan": {
    "code": "pro",
    "name": "Pro",
    "monthly_price_cents": 2000,
    "included_emails": 50000,
    "overage_per_1000_cents": 60
  },
  "effective_plan_code": "pro",
  "subscription": {
    "status": "active",
    "current_period_start": "2026-09-01T00:00:00.000Z",
    "current_period_end": "2026-10-01T00:00:00.000Z",
    "invoicing": "automatic",
    "latest_invoice_status": "paid"
  },
  "usage": {
    "period_start": "2026-09-01T00:00:00.000Z",
    "period_end": "2026-10-01T00:00:00.000Z",
    "emails": 12840,
    "included_emails": 50000
  },
  "pay_as_you_go": {
    "enabled": false
  },
  "payment_method": {
    "present": true,
    "brand": "visa",
    "last4": "4242"
  },
  "addons": [],
  "pending_change": null,
  "checkout_available": false
}
  • An upgrade takes effect at once, with prorations. A downgrade is scheduled for the end of the period and shows in pending_change.
  • Free to paid is a checkout, and paid to free is a cancellation in the portal, so both are refused here.
  • A change that would turn off the team's single sign-on is refused for an API key: a signed-in admin makes it.

POST /billing/payg

Keep sending beyond the plan's included emails, billed per thousand, or stop at them.

Body

Body
FieldTypeDescription
enabled*booleantrue to continue beyond the quota, false to stop at it.
curl -X POST "https://api.rasket.com/billing/payg" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true
}'

Response 200

{
  "object": "billing_payg",
  "enabled": true,
  "overage_ceiling_emails": 500000
}
  • Switching it on needs a plan with an overage rate and a payment method on file; otherwise 422 validation_error names what is missing.
  • It raises only the monthly quota — never the daily cap or a restricted team's limits.

POST /billing/addons

Buy an add-on, or set how many units of it the team holds.

Body

Body
FieldTypeDescription
addon_code*stringextra_domains_100, dedicated_ip or sso.
quantityintegerUnits held after the call, 1–100. A set, not an increment. Defaults to 1.
2 more fields (region, expected_daily_volume)
Body, less common
FieldTypeDescription
regionstringdedicated_ip only: the region the pool should live in.
expected_daily_volumeintegerdedicated_ip only: roughly how many emails a day the pool will carry.
curl -X POST "https://api.rasket.com/billing/addons" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "addon_code": "extra_domains_100",
  "quantity": 1
}'

Response 200

{
  "object": "team_addon",
  "addon_code": "extra_domains_100",
  "name": "100 extra domains",
  "monthly_price_cents": 2000,
  "quantity": 1,
  "status": "active",
  "requested_at": "2026-09-12T10:00:00.000Z",
  "provisioned_at": "2026-09-12T10:00:00.000Z"
}
  • Every add-on is an item on the paid subscription, so a team without an active paid plan is 422 validation_error.
  • dedicated_ip is a request: it is billed from now, and its status moves from requested to provisioning to active as an operator sets it up.
  • sso needs the Scale plan or above.

DELETE /billing/addons/{addon_code}

Take an add-on off the subscription, with a proration.

Path parameters

Path parameters
FieldTypeDescription
addon_code*stringextra_domains_100, dedicated_ip or sso.
curl -X DELETE "https://api.rasket.com/billing/addons/extra_domains_100" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "team_addon",
  "addon_code": "extra_domains_100",
  "name": "100 extra domains",
  "monthly_price_cents": 2000,
  "quantity": 1,
  "status": "canceled",
  "requested_at": "2026-09-12T10:00:00.000Z",
  "provisioned_at": "2026-09-12T10:00:00.000Z"
}
  • Removing extra_domains_100 deletes no domain: the lower limit only refuses the next one you add.
  • Removing sso turns single sign-on off, so an API key is refused it with 403: a signed-in admin removes it.