---
title: "Invoice a company for a plan"
method: POST
path: "/companies/{id}/plan-invoices"
tags: ["Companies"]
---

# Invoice a company for a plan

`POST /companies/{id}/plan-invoices`

## Request

- Base URL: `https://api.cavuno.com/v1`
- URL: `https://api.cavuno.com/v1/companies/{id}/plan-invoices`
- Auth: HTTP bearer

Sells a membership plan to a company by invoice, the same as **Generate invoice** in the company plan dialog. Issues a Stripe invoice on the board's connected account, due in 30 days, and returns its `hostedInvoiceUrl`. Cavuno does not send the invoice itself; share the link with the employer. The plan is assigned to the company when the invoice is paid. The plan's own price and, for a subscription plan, its interval set the terms; for a plan without a price, send `amountCents` and `currency`. Send `billingInterval` to make the invoice recur. A company holds one membership at a time, so a company with an active membership or another membership invoice awaiting payment gets 409 `company_plan_membership_taken`. Requires an `Idempotency-Key` header: a retry with the same key returns the first response and never issues a second invoice. Requires `plans.manage`.

## Path parameters

- `id` string, required: The company's opaque object `id`: the `id` field returned by the company endpoints (e.g. `p17xq4c2b9r5gz3e8j0r6t4k7m2n9d5w`). This is NOT the `slug` or display name; passing a slug like `antler` returns 404. To look up a company by name or slug, use `GET /v1/companies`, `POST /v1/companies/search`, or `POST /v1/companies/find-or-create` and read the returned `id`.

## Request body

- CreateCompanyPlanInvoiceBody
  - `planId` string, required: An active (not archived) plan with `purpose: membership` on this board.
  - `contactEmail` string, email, required: Billing email of the employer who will pay.
  - `amountCents` integer: Amount per invoice in currency minor units. Required when the plan has no price; rejected when it has one.
  - `currency` string: ISO 4217 currency code. Send it together with `amountCents`.
  - `billingInterval` 'month' | 'year': Make the invoice recur. Omit for a one-off invoice. Rejected when the plan is a subscription with its own interval, which then sets the terms.
  - `billingIntervalCount` integer: Intervals between invoices (default 1; `month` with 3 is quarterly). Needs `billingInterval`.

## Response `201`

The issued plan invoice.

- CompanyPlanInvoice
  - `id` string, required: The plan invoice ID.
  - `object` 'company_plan_invoice', required
  - `companyId` string, required
  - `planId` string, required
  - `planName` string, nullable, required
  - `status` 'issued' | 'paid' | 'canceled' | 'uncollectible', required: `issued`: awaiting payment. `paid`: paid, and the plan was assigned to the company. `canceled`: canceled before payment. `uncollectible`: marked uncollectible in Stripe.
  - `amountCents` integer, required: Amount per invoice, in currency minor units.
  - `currency` string, required: Lowercase ISO 4217 currency code.
  - `billingInterval` 'month' | 'year', nullable, required: Null for a one-off invoice; otherwise how often it recurs.
  - `billingIntervalCount` integer, nullable, required: Number of intervals between invoices (3 months = quarterly).
  - `contactEmail` string, required: The billing email the invoice is for.
  - `hostedInvoiceUrl` string, nullable, required: The payment page for the employer. Cavuno does not send the invoice itself; share this link with them.
  - `dueAt` string, nullable, required: ISO-8601 due date (30 days after issue).
  - `assignmentId` string, nullable, required: The plan assignment created when the invoice was paid; null until then.
  - `createdAt` string, required: ISO-8601 creation time.
  - `updatedAt` string, required: ISO-8601 time of the last change.

## Other responses

- `400`: Invalid body, a plan without a price and no amount (`company_plan_invoice_amount_required`), or an amount or interval for a plan that sets its own (`company_plan_invoice_terms_fixed`).
- `404`: Company or plan not found.
- `409`: Stripe is not connected (`company_plan_invoice_connect_required`), the plan is archived or not a membership plan (`company_plan_not_assignable`), or the company already has a membership or a membership invoice awaiting payment (`company_plan_membership_taken`).
- `502`: Stripe could not confirm the invoice (`company_plan_invoice_stripe_error`). An invoice may still have been issued: list the company's plan invoices before trying again with a new Idempotency-Key.

## Changes

- **2026-10-20** `de39bd3242e2`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/cavuno/apis/cavuno-v1-rest-api/changes/companies/:id/plan-invoices/post.md)

---

[API](https://skmtc.dev/cavuno/apis/cavuno-v1-rest-api.md) · [All operations](https://skmtc.dev/cavuno/apis/cavuno-v1-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cavuno/apis/cavuno-v1-rest-api/revisions/d5974dabff14?raw)
