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
| Scope | Grants access to |
|---|---|
read:plans | plans.list, plans.get |
write:plans | plans.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:
| Status | Meaning |
|---|---|
active | The recurring engine generates invoices on schedule. |
paused | Invoice generation is suspended. The plan still exists and can be resumed later. |
cancelled | Permanent. 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.
Filter to one lifecycle state. One of active, paused, or cancelled.
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.
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.
The customer this plan belongs to. Must exist in this tenant. Cannot be changed after creation.
A label for the plan (e.g. Monthly lawn care). Max 200 characters.
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.
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.
Line item description (e.g. Mow front and back). Max 200 characters.
Quantity. Must be a positive number.
Unit price in dollars (e.g. 50 for $50.00). Must be zero or positive.
Whether this line item is taxable. Defaults to true.
Override the plan's default tax rate for this specific line item. Pass null to use the plan-level default.
How many days before the due date to generate the draft invoice. Integer between 0 and 365. Defaults to 0.
The default tax rate to apply to taxable line items at invoice generation time. Pass null to use no default.
The customer's saved payment method to charge automatically. Pass null to leave unset.
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.
Internal notes about the plan. Max 2,000 characters. Pass null to clear.
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.
The plan to update.
Updated title. Max 200 characters.
Updated rrule. Changing this recomputes next_invoice_at. Max 500 characters.
Updated lead offset. Integer between 0 and 365.
Updated default tax rate. Pass null to clear.
Updated payment method. Pass null to clear.
Updated auto-charge setting.
Updated internal notes. Max 2,000 characters. Pass null to clear.
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.
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
| Error | What it means | Common cause |
|---|---|---|
invalid_input | A field failed validation, or the token type is not allowed | Missing a required field; empty items array; passing customer_id or status in plans.update; calling a write tool with a tenant-key token |
not_found | The plan does not exist in this tenant | Wrong or outdated id |
conflict | The request conflicts with the plan's current state | Calling plans.cancel on a plan that is already cancelled |
insufficient_scope | The token does not have the required scope | Missing read:plans or write:plans on the token |
For the full error shape and HTTP status codes, see Errors.