> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instapods.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Billing

> Manage subscriptions, payment methods, invoices, and credits.

All billing is scoped to the active team. Amounts are in **cents** (e.g., `300` = \$3.00).

## Subscription

### Get Subscription

```
GET /api/billing/subscription
```

**Response: `200 OK`**

```json theme={null}
{
  "id": "sub_abc123",
  "team_id": "team_xyz",
  "plan_slug": "build",
  "status": "active",
  "current_period_start": "2026-02-01T00:00:00Z",
  "current_period_end": "2026-03-01T00:00:00Z",
  "created_at": "2026-01-15T10:00:00Z"
}
```

**Subscription statuses:** `active`, `cancelled`, `past_due`, `trialing`

### Subscribe

```
POST /api/billing/subscribe
```

```json theme={null}
{
  "plan_slug": "build"
}
```

**Response: `201 Created`** — returns the subscription object.

### Change Plan

```
PUT /api/billing/subscription/plan
```

```json theme={null}
{
  "plan_slug": "grow"
}
```

Changes take effect immediately. Billing is prorated.

### Cancel

```
POST /api/billing/subscription/cancel
```

**Response: `200 OK`**

```json theme={null}
{ "status": "cancelled" }
```

Only the team owner can cancel — cancelling ends billing for everyone on the
team. A non-owner member gets `403 Forbidden`.

**Every pod on the team must be deleted first.** Stopping a pod does not
qualify: a stopped pod is still billed at its full plan rate, and only deletion
ends the charge. While any pod remains, the call returns `409 Conflict` with the
pods that are in the way:

```json theme={null}
{
  "error": "delete your 2 remaining pod(s) before cancelling: api, worker",
  "blocking_pods": ["api", "worker"]
}
```

Cancelling stops invoicing immediately, so usage already accrued in the current
partial period is not invoiced. Nothing else is removed — the team, its invoice
history and any remaining credits are untouched.

There is no separate "resubscribe" call: creating a pod on a cancelled team
starts a new subscription automatically.

### Resume

```
POST /api/billing/subscription/resume
```

Returns a `past_due` or `suspended` subscription to `active` once every unpaid
invoice is settled; it refuses while any remain. It does **not** reverse a
cancellation — a cancelled subscription is gone, and deploying a pod is what
starts a new one.

***

## Invoices

### List Invoices

```
GET /api/billing/invoices
```

**Response: `200 OK`**

```json theme={null}
[
  {
    "id": "inv_abc123",
    "status": "paid",
    "amount_due": 700,
    "amount_paid": 700,
    "currency": "usd",
    "period_start": "2026-02-01T00:00:00Z",
    "period_end": "2026-03-01T00:00:00Z",
    "paid_at": "2026-02-05T10:30:00Z",
    "created_at": "2026-02-01T00:00:00Z"
  }
]
```

**Invoice statuses:** `draft`, `open`, `paid`, `void`, `uncollectible`

### List Open Invoices

```
GET /api/billing/invoices/open
```

Just the unpaid ones for the active team. This is what the dashboard's "you have an unpaid invoice"
banner reads, and it's the cheap way to check whether an account is about to be suspended for
non-payment.

Returns the same array shape as `GET /api/billing/invoices`, or `[]` when nothing is outstanding.

### Get Invoice

```
GET /api/billing/invoices/{invoiceId}
```

Returns the full invoice object including line items.

### Pay an Invoice

```
POST /api/billing/invoices/{invoiceId}/pay
```

Charges the team's default payment method for an open invoice immediately, instead of waiting for
the next automatic retry. No request body.

**Response: `200 OK`** - returns the updated invoice, with `status: "paid"` on success.

**On failure: `400 Bad Request`**

```json theme={null}
{
  "error": "your card was declined",
  "invoice_id": "inv_abc123",
  "hosted_invoice_url": "https://invoice.stripe.com/i/..."
}
```

`hosted_invoice_url` is present only when the charge reached Stripe and failed there. Send the
customer to that page when you get it: it handles 3-D Secure, mandates and alternative payment
methods that a direct charge cannot. It is absent when the invoice was never chargeable in the first
place (already paid, voided, or no Stripe invoice behind it).

The invoice must belong to your active team, or the call returns `403`.

***

## Payment Methods

### List Payment Methods

```
GET /api/billing/payment-methods
```

**Response: `200 OK`**

```json theme={null}
[
  {
    "id": "pm_abc123",
    "type": "card",
    "card_brand": "visa",
    "card_last4": "4242",
    "card_exp_month": 12,
    "card_exp_year": 2027,
    "is_default": true,
    "created_at": "2026-01-15T10:00:00Z"
  }
]
```

### Add Payment Method

```
POST /api/billing/payment-methods
```

```json theme={null}
{
  "stripe_payment_method_id": "pm_xxx"
}
```

**Response: `201 Created`** — returns the payment method with card details fetched from Stripe.

### List Cards on Your Other Teams

```
GET /api/billing/payment-methods/other-teams
```

Cards already on file for **other teams you own**, so a new team can reuse one instead of making you
re-enter it. Read-only: adding the card to this team is still a `POST /api/billing/payment-methods`.

**Response: `200 OK`**

```json theme={null}
[
  {
    "team_id": "team_abc",
    "team_name": "Side Projects",
    "payment_methods": [
      { "id": "pm_abc123", "card_brand": "visa", "card_last4": "4242", "is_default": true }
    ]
  }
]
```

Teams where you are a member rather than the owner are excluded, as are teams with no cards, so an
empty array is the normal answer for most accounts.

### Set Default

```
POST /api/billing/payment-methods/default
```

```json theme={null}
{
  "payment_method_id": "pm_abc123"
}
```

### Remove

```
DELETE /api/billing/payment-methods/{pmId}
```

### Create SetupIntent

```
POST /api/billing/setup-intent
```

**Response: `200 OK`**

```json theme={null}
{
  "client_secret": "seti_xxx_secret_yyy"
}
```

Used by the frontend Stripe Elements flow to securely collect card details.

***

## Credits & Upcoming Charges

### Get Credits

```
GET /api/billing/credits
```

```json theme={null}
{
  "available": 5000,
  "credits": [
    {
      "id": "cred_abc",
      "amount": 5000,
      "remaining": 2000,
      "type": "promo",
      "reason": "Welcome bonus",
      "expires_at": "2026-12-31T23:59:59Z"
    }
  ]
}
```

**Credit types:** `promo`, `bonus`, `refund`

### Preview Upcoming Charges

```
GET /api/billing/upcoming
```

```json theme={null}
{
  "charges": [
    {
      "pod_name": "my-app",
      "pod_status": "running",
      "plan_slug": "build",
      "plan_name": "Build",
      "monthly_rate": 700,
      "daily_rate": 23,
      "active_days": 20,
      "amount": 460
    }
  ],
  "subtotal": 460,
  "credits_available": 200,
  "estimated_total": 260,
  "period_start": "2026-02-01T00:00:00Z",
  "period_end": "2026-03-01T00:00:00Z"
}
```

<Note>
  Pods are billed daily based on their plan. Stopped pods still incur charges — only deleting a pod stops billing.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.