# Iblai API Billing

> iblai-api-billing

- Skill: `iblai/iblai-api-billing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add iblai/iblai-api-billing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/iblai/iblai-api-billing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: iblai (https://skillmd.com/u/iblai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/iblai/iblai-api-billing

---


# iblai-api-billing

Operate the organization's **billing, credits, and item-paywall** surface from
the API: credit accounts (display balance, auto-recharge preferences, manual
top-up), transaction history, per-item paywall configuration and price tiers,
Stripe-Connect checkout (authenticated + guest), subscriptions, access checks,
public pricing, and platform-wide reporting. Paywalls are **generic**: every
endpoint keys off `item_type` + `item_id`, so the same surface sells agents,
courses, programs, pathways, or any custom item type. Use when reading a credit
balance, enabling a paywall and adding prices, generating a checkout link,
checking whether a user has access, or pulling subscriber/revenue reports.

## Auth & conventions

- **Base URL:** `https://api.iblai.app/dm` — these are Data Manager (DM)
  endpoints, so the **`/dm` prefix is required**; the `/api/billing/...` paths
  below are appended to it (e.g.
  `https://api.iblai.app/dm/api/billing/account/`). Omitting `/dm` will not
  resolve.
- **Header:** `Authorization: Api-Token $IBLAI_API_KEY` on every request.
- **Path vars:** `{platform_key}` = `$IBLAI_ORG` (the org key; also called
  `org` / `platform_org` elsewhere on the wire). `{item_type}` = the item kind
  (`mentor`, `course`, `program`, `pathway`, or a custom type — see Notes).
  `{item_id}` = the item's identifier (e.g. an agent slug, course id, or
  `config`/`price` UUID for the by-id variants).
- DELETE / destructive / outward-facing calls (paywall writes, price create/
  update/delete, checkout, subscribe, cancel) say "Confirm with the user
  first."
- **Permission tiers** vary by endpoint and are noted inline: *authenticated
  user* (own account), *platform admin / CanSellItems* (paywall config,
  prices, reporting), and *public / no-auth* (public pricing, guest checkout,
  Stripe callback). See Notes.
- Not connected yet? Run **`/iblai-api-login`** first to populate `IBLAI_ORG`,
  `IBLAI_USERNAME`, and `IBLAI_API_KEY`.

## Reads

### Credit account & transactions

- **GET** `/api/billing/account/` — current user's credit account info:
  `has_credits`, `account_id`, `available_credits`, auto-recharge fields,
  plan info (`current_plan`, `previous_plan`, `free_trial`,
  `can_use_auto_recharge`), `has_payment_method`, `is_owner`, and
  `pricing_table`/`message` when applicable. Pass `platform_key` query param to
  read a **platform** account instead of your own — requires platform admin, a
  platform API token, or `Ibl.Billing/Credits/read`; plain members get minimal
  info; non-members are denied.
- **GET** `/api/billing/transactions/` — paginated transaction history for the
  account (only user-facing fields: `payment_amount_usd`, `credits_amount`,
  `credits_balance_after`, `description`). Query params: `platform_key` (omit
  for own account; read permission enforced), `transaction_type`
  (`add`|`subtract`|`reserve`|`release`|`rollover`|`refund`),
  `from_date`/`to_date` (`YYYY-MM-DD`, inclusive), `page`, `page_size`
  (default 20, max 100).

### Paywall config & prices (platform admin / CanSellItems)

- **GET** `/api/billing/platforms/{platform_key}/items/{item_type}/{item_id}/paywall/`
  — read the item's paywall config; returns a default disabled config when none
  exists.
- **GET** `…/{item_type}/{item_id}/paywall/prices/` — list active price tiers
  (sorted by `sort_order`, then `amount`).
- **GET** `…/paywall/prices/{price_id}/` — get one price by its UUID.

### Subscriptions

- **GET** `…/{item_type}/{item_id}/subscription/` — the current user's
  subscription to this item. `404` if none.
- **GET** `/api/billing/platforms/{platform_key}/my-subscriptions/` — paginated
  list of the current user's subscriptions on the platform. Query params:
  `status` (subscription status — see Notes), `item_type`, `search` (matches
  `item_id`), `page`, `page_size`.

### Access checks

- **GET** `/api/billing/access-check/{item_type}/{item_id}/` — does the
  authenticated user have payment access to the item? Returns `200` with
  `{has_access, item_type, item_id, reason, requires_payment,
  pricing_available, pricing, subscription}` when access is granted, or
  **`402` Payment Required** (same body, `has_access:false`, with `pricing`)
  when payment is needed. Resolves the platform from the `platform_key` query
  param or request context. Items with no registered paywall backend grant
  access by default.
- **GET** `…/{item_type}/{item_id}/access-check/` (platform-scoped) — same
  check, with `platform_key` taken from the path instead of a query param.

### Public pricing (no auth)

- **GET** `…/{item_type}/{item_id}/pricing/` — **public / no-auth** pricing for
  an item: `{item_type, item_id, item_name, is_paywalled, allow_free_tier,
  trial_period_days, prices[]}`. Returns sensible defaults when no paywall
  config exists.
- **GET** `/api/billing/items/{config_unique_id}/public-pricing/` —
  **public / no-auth** variant that resolves the item from the paywall config's
  UUID, then returns the same payload.

### Checkout (Stripe Connect)

- **GET** `…/{item_type}/{item_id}/checkout-callback/{checkout_session_id}/`
  and **GET** `…/{item_type}/{item_id}/checkout-callback/` — **public / no-auth**
  endpoint Stripe redirects to after checkout. Verifies the session, processes
  completion (creates/updates the subscription, same path as the webhook), and
  `302`-redirects to the return URL. Session id comes from the path or the
  `checkout_session_id`/`stripe_checkout_id` query param; `return_url` query
  param optional. Not called directly — Stripe drives it.

### Platform reporting (platform admin / CanSellItems)

- **GET** `…/{item_type}/{item_id}/subscribers/` — paginated subscribers for one
  item. Query params: `status` (subscription status), `search` (matches
  username / email / item_id), `page`, `page_size`.
- **GET** `/api/billing/platforms/{platform_key}/subscribers/` — paginated
  subscribers across **all** items on the platform. Query params: `status`,
  `item_type`, `search`, `page`, `page_size`.
- **GET** `/api/billing/platforms/{platform_key}/paywalls/` — paginated list of
  all paywall configs on the platform. Query params: `item_type`, `is_enabled`
  (boolean), `search` (matches `item_id` / `description`), `page`, `page_size`.
- **GET** `/api/billing/platforms/{platform_key}/revenue/` — aggregate sales
  summary across all items: `{sales_volume, sales_count, currency}` (succeeded
  payments only).

## Writes

### Credit account & transactions

- **PUT** / **PATCH** `/api/billing/account/` — update auto-recharge preferences
  on your own account (PATCH is identical to PUT). Body fields (all optional):
  ```json
  {
    "auto_recharge_enabled": "boolean",
    "auto_recharge_threshold_usd": "decimal (>= 0)",
    "auto_recharge_amount_usd": "decimal (min 0.50)",
    "auto_recharge_spending_limit_usd": "decimal (0 = unlimited)",
    "platform_key": "string (read-only in this context)"
  }
  ```
  When enabling, missing values are auto-filled (only-limit → amount = 20% of
  limit; only-amount → limit = 5× amount; neither → limit 20 / amount 4).
  Writing to a platform account requires write permission (admin / API token /
  `Ibl.Billing/Credits/write`).
- **POST** `/api/billing/auto-recharge/trigger/` — run a charge once.
  With `amount_usd`: **manual top-up** (charge that amount, add credits; no
  threshold/limit/cooldown). Without it: run auto-recharge once if enabled and
  below threshold. Returns `{status: "triggered"|"skipped"}`; `400` if skipped
  or failed (e.g. no payment method, cooldown, spending-limit exceeded).
  Confirm with the user first (it charges a card).
  ```json
  { "amount_usd": "decimal (optional)", "platform_key": "string (optional, default 'main')" }
  ```

### Paywall config & prices (platform admin / CanSellItems)

- **POST** / **PUT** `…/{item_type}/{item_id}/paywall/` — create or update the
  paywall config (PUT is identical to POST). Creates a Stripe product on first
  enable (requires a payment-ready Stripe Connect account). Confirm with the
  user first. Body (all optional):
  ```json
  {
    "is_enabled": "boolean",
    "allow_free_tier": "boolean",
    "trial_period_days": "integer (>= 0)",
    "description": "string",
    "on_successful_payment": "url (<= 500 chars)",
    "grandfathering_strategy": "free_forever | require_subscription (default require_subscription)",
    "item_name": "string",
    "item_metadata": "object"
  }
  ```
- **DELETE** `…/{item_type}/{item_id}/paywall/` — disable the paywall (sets
  `is_enabled=False`; does **not** delete the config). Returns `204`. Confirm
  with the user first.
- **POST** `…/{item_type}/{item_id}/paywall/prices/` — create a price tier.
  Requires the paywall enabled and a payment-ready Connect account; creates the
  Stripe price. Returns `201`. Confirm with the user first.
  ```json
  {
    "amount": "decimal (>= 0, required)",
    "currency": "string (default 'usd')",
    "interval": "month | year | one_time (default month)",
    "name": "string", "description": "string",
    "is_active": "boolean", "features": "object/list",
    "remark": "string", "sort_order": "integer"
  }
  ```
- **PUT** `…/paywall/prices/{price_id}/` — update a price (partial). If pricing
  fields (`amount`/`currency`/`interval`) change and a Stripe price exists, a
  new Stripe price is created and the old one deactivated. Confirm with the
  user first.
- **DELETE** `…/paywall/prices/{price_id}/` — soft-delete the price tier and
  deactivate its Stripe price. Returns `204`. Confirm with the user first.

### Checkout (Stripe Connect)

- **POST** `…/{item_type}/{item_id}/checkout/` — create a Stripe checkout
  session for the **authenticated** user. Returns
  `{checkout_url, session_id, platform_key}`. `400` if the user already has an
  active subscription. Requires a payment-ready Connect account and a Stripe-
  configured price. Confirm with the user first.
  ```json
  { "price_id": "uuid (required)", "success_url": "url", "cancel_url": "url" }
  ```
- **POST** `…/{item_type}/{item_id}/checkout-guest/` — **public / no-auth**
  guest checkout; `email` is required. Same response shape. Confirm with the
  user first.
  ```json
  { "price_id": "uuid (required)", "email": "email (required)", "success_url": "url", "cancel_url": "url" }
  ```
- **POST** `/api/billing/prices/{price_unique_id}/checkout-guest/` —
  **public / no-auth** guest checkout that resolves the platform / item_type /
  item_id from the price's UUID, then runs the standard guest flow (`email`
  required; the price's paywall must be enabled). Confirm with the user first.

### Subscriptions

- **POST** `…/{item_type}/{item_id}/subscription/cancel/` — cancel the current
  user's subscription. For recurring subscriptions returns a Stripe customer-
  portal URL (`{portal_url}`); for one-time purchases cancels directly and
  returns the subscription. `404` if none. Confirm with the user first.
  ```json
  { "return_url": "url (optional)" }
  ```

### Credit consumption (sample)

- **POST** `/api/billing/credits/sample-action/` — sample endpoint protected by
  the `@consume_credits` decorator (authenticated). Checks balance first: if
  insufficient, returns **`402`** with a `pricing_table`; otherwise runs and
  consumes 1 credit, returning `{status, message, ...balance}`. This is a
  reference/test endpoint demonstrating the credit-gating pattern, not a
  production action.

## Example

Enable a paywall on a course-type item and read its public pricing (note the
`/dm` prefix). Confirm the paywall write with the user first:

```bash
curl -X POST \
  "https://api.iblai.app/dm/api/billing/platforms/$IBLAI_ORG/items/course/course-v1:ACME+CS101+2024/paywall/" \
  -H "Authorization: Api-Token $IBLAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_enabled": true, "trial_period_days": 7, "allow_free_tier": true}'
```

## Notes

- **item_type values.** Built-in types are `mentor`, `course`, `program`, and
  `pathway`. Any other value is normalized to a `custom:<slug>` namespace
  (e.g. `workshop` → `custom:workshop`); the type must start with a letter
  after slugification or the request `400`s. Do not treat this as a
  agent-only paywall — it is the generic item-paywall surface for any sellable
  item.
- **Stripe Connect is required** for monetary operations. Enabling a paywall,
  creating/updating prices, and checkout all need the platform's Stripe Connect
  account to exist and be payment-ready; otherwise you get a `400`/validation
  error. Checkout returns a Stripe-hosted `checkout_url`; cancellation of a
  recurring subscription returns a Stripe customer-portal URL.
- **402 Payment Required** is meaningful: `GET access-check/...` returns `402`
  (not `403`) when the user lacks access and must pay, and the
  `@consume_credits`-gated sample endpoint returns `402` with a `pricing_table`
  when the balance is too low.
- **Permission tiers.**
  - *Authenticated user* (own data): `account/`, `auto-recharge/trigger/`,
    `transactions/`, `access-check/...`, `subscription/`, `subscription/cancel/`,
    `my-subscriptions/`, `checkout/`, `credits/sample-action/`. Reading/writing
    another (platform) credit account additionally requires admin / platform API
    token / `Ibl.Billing/Credits/read|write`.
  - *Platform admin + CanSellItems*: all paywall config + price endpoints and
    the platform reporting endpoints (`subscribers/`, `paywalls/`, `revenue/`).
    These enforce `IsPlatformAdmin` and the `Ibl.Billing/CanSellItems/action`
    RBAC policy (when RBAC is enabled) or a matching platform API key.
  - *Public / no-auth*: `pricing/`, `items/{config_unique_id}/public-pricing/`,
    `checkout-guest/` (both variants), and `checkout-callback/` (Stripe
    redirect). These explicitly allow unauthenticated access.
- **Subscription status** values used as the `status` filter / in responses:
  `active`, `free`, `grandfathered`, `trialing`, `past_due`, `canceled`,
  `incomplete`.
- **Price `interval`** values: `month`, `year`, `one_time`. `one_time` prices
  checkout in Stripe `payment` mode; recurring prices use `subscription` mode
  and honor `trial_period_days`.
- **Async / webhooks.** Checkout completion is processed both via the
  `checkout-callback/` redirect and via Stripe webhooks (the same handler), so a
  subscription may be created without your code polling. The callback enriches
  its return URL with `email`, `platform_key`, and `subscription_id`.
- **Pagination.** `transactions/` uses page-number pagination (default
  `page_size` 20, max 100); the reporting/list endpoints (`subscribers/`,
  platform `subscribers/`, `paywalls/`, `my-subscriptions/`) use the standard
  page-number paginator. Internal USD amounts are never exposed in transaction
  history — only the user-facing payment/credit fields.

