API referencePlans

Plans

Define recurring billing plans — cadence, line items, and customer — via the Zoop MCP tool layer.

A plan is a recurring billing template. It stores three things: a billing cadence (how often to invoice), one or more line items (what to charge for), and a customer to invoice. Zoop's recurring engine reads your active plans and automatically creates draft invoices on schedule.

The plan tools let you create and manage those templates. They are definition-only — none of them charges a card or triggers a payment.

Plans require their own permission scopes: read:plans and write:plans. There is no shortcut alias that covers both; you must request them explicitly when setting up your token. See Scopes for the full scope catalog.

If you are new to the MCP layer, start at MCP overview to understand how to connect and authenticate before calling tools.


Scopes

ScopeGrants access to
read:plansplans.list, plans.get
write:plansplans.create, plans.update, plans.cancel

write:plans does not include read:plans. If your integration needs to both read and write plans, request both scopes.


Key concepts

These tools only define plans — they do not charge anyone. plans.create, plans.update, and plans.cancel are database operations. None of them charges a card, creates a Stripe subscription, or triggers the recurring engine. Charging happens separately, outside MCP v1.

The customer cannot change after creation. Once a plan is created, its customer_id is locked. If you try to pass customer_id in an update — or try to set status directly — the call returns an invalid_input error. To cancel a plan, use plans.cancel instead.

Updating line items replaces the whole list. When you include items in plans.update, Zoop replaces the entire item list. To add one line item, re-send all the existing items plus the new one.

Cancelling is permanent. plans.cancel sets the plan's status to cancelled. There is no way to resume a cancelled plan, and no refund is issued. If you might want to pause instead of cancel, see the plan lifecycle section below.

Write tools need a user token. plans.create, plans.update, and plans.cancel all require a user actor — Zoop needs a user ID to record who made the change. Tokens from tenant-bound API keys (which have no user ID attached) cannot call these tools. Use a user-bound API key or an OAuth token instead.


Plan lifecycle

A plan moves through three statuses:

StatusMeaning
activeThe recurring engine generates invoices on schedule.
pausedInvoice generation is suspended. The plan still exists and can be resumed later.
cancelledPermanent. The engine stops generating invoices and the plan cannot be reactivated.

Pause and resume are not available as MCP tools in v1. They live on separate action routes. See MCP overview — limitations for the full list of what is out of scope.


Tools

plans.list

List recurring plans for the tenant, most recent first. Returns a page of plan records (each with its id and summary fields) and pagination metadata.

body
status

Filter to one lifecycle state. One of active, paused, or cancelled.

body
page

1-based page number. Omit for the first page.

Example

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "plans.list",
    "arguments": {
      "status": "active",
      "page": 1
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{"data":[{"id":"p1b2c3d4-...","title":"Monthly lawn care","status":"active","customer_id":"c9d8e7f6-...","cadence_rrule":"FREQ=MONTHLY;INTERVAL=1","next_invoice_at":"2026-07-01T00:00:00.000Z"}],"count":1,"page":1,"limit":20}"
      }
    ]
  }
}

plans.get

Fetch a single recurring plan by id. The response includes the plan's line items in sort order.

body
id

The plan's id. You can get this from plans.list.

Returns a not_found error if the plan does not exist in this tenant.


plans.create

Create a recurring plan template. The plan starts in active status and Zoop calculates next_invoice_at from the cadence you provide. No charge is made at creation time.

plans.create requires a user actor. Tokens issued from tenant-bound API keys cannot call this tool. Use a user-bound API key or an OAuth token.

body
customer_id

The customer this plan belongs to. Must exist in this tenant. Cannot be changed after creation.

body
title

A label for the plan (e.g. Monthly lawn care). Max 200 characters.

body
cadence_rrule

The billing cadence as an RFC 5545 rrule string (e.g. FREQ=MONTHLY;INTERVAL=1 for monthly billing). Max 500 characters. Zoop uses this string to calculate next_invoice_at and to schedule future invoices.

body
items

The line items for the plan. Must contain at least one entry. When the engine generates an invoice, these items become the invoice's line items.

body
lead_offset_days

How many days before the due date to generate the draft invoice. Integer between 0 and 365. Defaults to 0.

body
default_tax_rate_id

The default tax rate to apply to taxable line items at invoice generation time. Pass null to use no default.

body
payment_method_id

The customer's saved payment method to charge automatically. Pass null to leave unset.

body
auto_charge_enabled

Whether to charge the saved payment method automatically when the invoice is generated. Defaults to false. Only takes effect if the customer has an active auto-pay consent on file.

body
notes

Internal notes about the plan. Max 2,000 characters. Pass null to clear.

body
start_date

Explicit anchor date for computing next_invoice_at. Defaults to the current time when omitted.

Example

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "plans.create",
    "arguments": {
      "customer_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "title": "Monthly lawn care",
      "cadence_rrule": "FREQ=MONTHLY;INTERVAL=1",
      "items": [
        {
          "description": "Mow front and back",
          "quantity": 1,
          "unit_price": 75,
          "is_taxable": true
        }
      ],
      "lead_offset_days": 3,
      "auto_charge_enabled": false
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{"id":"p9a8b7c6-...","title":"Monthly lawn care","status":"active","customer_id":"cccccccc-...","cadence_rrule":"FREQ=MONTHLY;INTERVAL=1","next_invoice_at":"2026-07-13T00:00:00.000Z","lead_offset_days":3,"auto_charge_enabled":false,"items":[{"description":"Mow front and back","quantity":1,"unit_price":75,"is_taxable":true}],"created_at":"2026-06-13T18:00:00.000Z"}"
      }
    ]
  }
}

plans.update

Update a recurring plan's template fields. This is a partial update — only the fields you send change. No charge is made.

plans.update requires a user actor. Tokens issued from tenant-bound API keys cannot call this tool. Use a user-bound API key or an OAuth token.

Do not pass customer_id or status in the update body — the schema rejects them and returns an invalid_input error. To cancel a plan, use plans.cancel.

body
id

The plan to update.

body
title

Updated title. Max 200 characters.

body
cadence_rrule

Updated rrule. Changing this recomputes next_invoice_at. Max 500 characters.

body
lead_offset_days

Updated lead offset. Integer between 0 and 365.

body
default_tax_rate_id

Updated default tax rate. Pass null to clear.

body
payment_method_id

Updated payment method. Pass null to clear.

body
auto_charge_enabled

Updated auto-charge setting.

body
notes

Updated internal notes. Max 2,000 characters. Pass null to clear.

body
items

Replacement line items. When present, replaces the entire existing item list — re-send all existing items plus any additions or removals. Must contain at least one entry. See plans.create for the item field reference.

Example — rename a plan and update its cadence

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "plans.update",
    "arguments": {
      "id": "p9a8b7c6-...",
      "title": "Bi-monthly lawn care",
      "cadence_rrule": "FREQ=MONTHLY;INTERVAL=2"
    }
  }
}

plans.cancel

Cancel a recurring plan. Once cancelled, the recurring engine stops generating invoices for the plan. No refund is issued. If you only want to pause invoice generation temporarily, use the pause action route instead (see the lifecycle callout above).

Cancelling is permanent. There is no way to resume a cancelled plan. If you cancel by mistake, you will need to create a new plan.

Calling plans.cancel on a plan that is already cancelled returns a conflict error.

plans.cancel requires a user actor. Tokens issued from tenant-bound API keys cannot call this tool. Use a user-bound API key or an OAuth token.

body
id

The plan to cancel.

Example

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "plans.cancel",
    "arguments": {
      "id": "p9a8b7c6-..."
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{"cancelled":true,"id":"p9a8b7c6-..."}"
      }
    ]
  }
}

Error reference

ErrorWhat it meansCommon cause
invalid_inputA field failed validation, or the token type is not allowedMissing a required field; empty items array; passing customer_id or status in plans.update; calling a write tool with a tenant-key token
not_foundThe plan does not exist in this tenantWrong or outdated id
conflictThe request conflicts with the plan's current stateCalling plans.cancel on a plan that is already cancelled
insufficient_scopeThe token does not have the required scopeMissing read:plans or write:plans on the token

For the full error shape and HTTP status codes, see Errors.