Edits a draft invoice. Only legal while in draft — use void plus create-new for finalized invoices. Totals recompute when line items change.
AuthorizationRequiredBearer <token>API key as Bearer token
In: header
application/jsonRequiredcustomerIdstringNew CRM contact id of the recipient
customerEmailstringUpdated snapshot of the recipient's email
customerFirstNamestringUpdated snapshot of the recipient's first name
customerLastNamestringUpdated snapshot of the recipient's last name
customerPhonestringUpdated snapshot of the recipient's phone
lineItemsarray<object>Replacement line items; totals will be recomputed
notesstringtaxIdsarray<object>Replaces the existing tax-ID snapshot in full. Pass [] to clear.
dueAtstringUpdated payment due date
"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"Format: "date-time"idRequiredstringInvoice id, discoverable via list_invoices
curl -X PATCH "https://api.3common.com/v1/invoices/string" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"customerId": "string",
"customerEmail": "string",
"customerFirstName": "string",
"customerLastName": "string",
"customerPhone": "string",
"lineItems": [
{
"description": "string",
"quantity": 1,
"unitAmount": 9007199254740991,
"productId": "string",
"priceId": "string",
"eventId": "string",
"taxAmount": 9007199254740991,
"productType": "product",
"productName": "string",
"components": [
{
"productId": "string",
"productName": "string",
"productType": "product",
"productImageUrl": "string",
"eventId": "string",
"eventImageUrl": "string",
"eventName": "string",
"eventStart": "2019-08-24T14:15:22Z",
"eventEnd": "2019-08-24T14:15:22Z",
"eventLocation": "string",
"eventTimezone": "string",
"seatingInformation": {
"sectionId": "string",
"rowId": "string",
"seatId": "string",
"seatReferenceId": "string",
"sectionReferenceId": "string",
"priceLevelId": "string"
},
"disableQrCode": true,
"willCall": true
}
],
"disableQrCode": true,
"willCall": true,
"productImageUrl": "string",
"eventName": "string",
"eventImageUrl": "string",
"eventStart": "2019-08-24T14:15:22Z",
"eventEnd": "2019-08-24T14:15:22Z",
"eventLocation": "string",
"eventTimezone": "string",
"seatingInformation": {
"sectionId": "string",
"rowId": "string",
"seatId": "string",
"seatReferenceId": "string",
"sectionReferenceId": "string",
"priceLevelId": "string"
},
"inventoryConsumedAt": "2019-08-24T14:15:22Z"
}
],
"notes": "string",
"taxIds": [
{
"type": "string",
"value": "string"
}
],
"dueAt": "2019-08-24T14:15:22Z"
}'{
"data": {
"id": "string",
"hostId": "string",
"customerId": "string",
"customerEmail": "string",
"customerFirstName": "string",
"customerLastName": "string",
"customerPhone": "string",
"number": "string",
"currency": "USD",
"lineItems": [
{
"description": "string",
"quantity": 1,
"unitAmount": 9007199254740991,
"productId": "string",
"priceId": "string",
"eventId": "string",
"taxAmount": 9007199254740991,
"productType": "product",
"productName": "string",
"components": [
{
"productId": "string",
"productName": "string",
"productType": "product",
"productImageUrl": "string",
"eventId": "string",
"eventImageUrl": "string",
"eventName": "string",
"eventStart": "2019-08-24T14:15:22Z",
"eventEnd": "2019-08-24T14:15:22Z",
"eventLocation": "string",
"eventTimezone": "string",
"seatingInformation": {
"sectionId": "string",
"rowId": "string",
"seatId": "string",
"seatReferenceId": "string",
"sectionReferenceId": "string",
"priceLevelId": "string"
},
"disableQrCode": true,
"willCall": true
}
],
"disableQrCode": true,
"willCall": true,
"productImageUrl": "string",
"eventName": "string",
"eventImageUrl": "string",
"eventStart": "2019-08-24T14:15:22Z",
"eventEnd": "2019-08-24T14:15:22Z",
"eventLocation": "string",
"eventTimezone": "string",
"seatingInformation": {
"sectionId": "string",
"rowId": "string",
"seatId": "string",
"seatReferenceId": "string",
"sectionReferenceId": "string",
"priceLevelId": "string"
},
"inventoryConsumedAt": "2019-08-24T14:15:22Z"
}
],
"payments": [
{
"id": "string",
"status": "succeeded",
"amount": 9007199254740991,
"paidAt": "2019-08-24T14:15:22Z",
"idempotencyKey": "string",
"note": "string",
"externalId": "string",
"chargeId": "string",
"failureCode": "string",
"failureMessage": "string",
"refunds": [
{
"id": "string",
"amount": 9007199254740991,
"refundedAt": "2019-08-24T14:15:22Z",
"idempotencyKey": "string",
"reason": "string",
"note": "string",
"externalRefundId": "string",
"ledgerEntryId": "string"
}
]
}
],
"subtotal": -9007199254740991,
"taxTotal": -9007199254740991,
"total": -9007199254740991,
"amountPaid": -9007199254740991,
"amountDue": -9007199254740991,
"status": "draft",
"autoCharge": true,
"notes": "string",
"taxIds": [
{
"type": "string",
"value": "string"
}
],
"issuedAt": "2019-08-24T14:15:22Z",
"dueAt": "2019-08-24T14:15:22Z",
"paidAt": "2019-08-24T14:15:22Z",
"voidedAt": "2019-08-24T14:15:22Z",
"subscriptionId": "string",
"quoteId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
}export interface Response {
data: {
id?: string;
hostId?: string;
/**
* CRM contact id of the recipient
*/
customerId?: string;
/**
* Snapshot of the recipient's email captured at create time
*/
customerEmail?: string;
/**
* Snapshot of the recipient's first name captured at create time
*/
customerFirstName?: string;
/**
* Snapshot of the recipient's last name captured at create time
*/
customerLastName?: string;
/**
* Snapshot of the recipient's phone captured at create time
*/
customerPhone?: string;
/**
* Sequential invoice number; null while in draft
*/
number?: string | null;
currency?: "USD" | "CAD";
lineItems?: {
/**
* Human-readable description shown on the invoice
*/
description: string;
/**
* Whole units billed
*/
quantity: number;
/**
* Per-unit price in the invoice currency, minor units (cents for USD)
*/
unitAmount: number;
/**
* Optional reference to a Product in the catalog
*/
productId?: string;
/**
* Optional reference to the Price doc that backed this line (subscription billing)
*/
priceId?: string;
/**
* Optional reference to an Event when the underlying product was event-attached
*/
eventId?: string;
/**
* Optional tax for this line, minor units
*/
taxAmount?: number;
/**
* Snapshot of the product type at line-creation time; drives post-payment fulfillment
*/
productType?: "product" | "add-on" | "bundle" | "donation" | "event-ticket";
/**
* Snapshot of the product display name at line-creation time
*/
productName?: string;
/**
* Snapshot of the bundle's components, flat-expanded by qty. Set
* only when `productType === "bundle"`. Carries each component's
* product id + display name + image URL + (when applicable) event
* + seating snapshot so the fulfillment pipeline can issue
* information-equivalent component tickets and the tickets PDF
* can render the legacy "ticket"-style block per item.
*/
components?: {
productId: string;
productName: string;
/**
* Catalog product type for the component — drives sort + visual treatment in the rendered bundle items list.
*/
productType?: "product" | "add-on" | "bundle" | "donation" | "event-ticket";
productImageUrl?: string;
/**
* Per-component parent event id — snapshotted onto the issued component ticket as legacy `event_id` so b2c availability and seat-occupation queries see the sale.
*/
eventId?: string;
/**
* Component event hero image — fallback artwork for the pulled-out ticket card when the component product has no image.
*/
eventImageUrl?: string;
eventName?: string;
/**
* ISO 8601 start datetime for the component's event/timeslot.
*/
eventStart?: string;
eventEnd?: string;
eventLocation?: string;
/**
* IANA timezone identifier for the component event — drives printed-date localization on the pulled-out ticket.
*/
eventTimezone?: string;
/**
* Slim venue seating assignment. The display labels (sectionId,
* rowId, seatId) are what the printed ticket shows; the reference
* ids are preserved for compatibility with the legacy seat-chart
* system.
*/
seatingInformation?: {
/**
* Section display label (e.g. "A").
*/
sectionId?: string;
/**
* Row display label (e.g. "5").
*/
rowId?: string;
/**
* Seat display label (e.g. "12").
*/
seatId?: string;
seatReferenceId?: string;
sectionReferenceId?: string;
priceLevelId?: string;
};
/**
* Component product's own disable_qr_code — the issued component ticket skips its QR (printable stub only).
*/
disableQrCode?: boolean;
/**
* Component product's own will_call — the issued component ticket uses will call fulfillment (no stub, collected at the venue).
*/
willCall?: boolean;
}[];
/**
* Snapshot of the underlying `Product.disable_qr_code` flag.
* When true, the issued ticket renders a "No scan" badge on the PDF
* and skips QR generation. Donations are scannable by default unless
* the host opts the product out via this flag.
*/
disableQrCode?: boolean;
/**
* Snapshot of the underlying `Product.will_call` flag. When true,
* the issued ticket uses will call fulfillment: no PDF stub is
* rendered, the ticket is not scannable, and the customer collects
* it at the venue. Independent of `disableQrCode`.
*/
willCall?: boolean;
/**
* Snapshot of the underlying `Product.image` URL — rendered as
* the ticket-stub artwork on the issued PDF. Absent products fall
* back to the event image (when set), then to a typographic
* placeholder.
*/
productImageUrl?: string;
/**
* Snapshot of the parent event's display name when the line was event-attached.
*/
eventName?: string;
/**
* Snapshot of the parent event's hero image URL — the renderer
* uses it as the ticket-stub artwork fallback when the product
* itself has no image.
*/
eventImageUrl?: string;
/**
* ISO 8601 start datetime for the parent event (or per-occurrence timeslot).
*/
eventStart?: string;
/**
* ISO 8601 end datetime for the parent event.
*/
eventEnd?: string;
/**
* Pre-formatted location label snapshotted at line creation.
*/
eventLocation?: string;
/**
* IANA timezone identifier for the parent event (e.g.
* `America/Los_Angeles`). Drives printed-date localization
* so the holder sees the event-local time + TZ abbreviation.
*/
eventTimezone?: string;
/**
* Per-line venue seating assignment. Mirrors the legacy
* `seatingInformation` field on a ticket so the new tickets
* PDF prints the same seat info the legacy PDF did.
*/
seatingInformation?: {
/**
* Section display label (e.g. "A").
*/
sectionId?: string;
/**
* Row display label (e.g. "5").
*/
rowId?: string;
/**
* Seat display label (e.g. "12").
*/
seatId?: string;
seatReferenceId?: string;
sectionReferenceId?: string;
priceLevelId?: string;
};
/**
* Stamped by the fulfillment pipeline once
* `InventoryRepository.consume()` has succeeded for this
* line. Drives per-line consume idempotency: a replayed
* fulfillment skips already-stamped lines and runs consume
* on unstamped ones, so a partial-failure state (tickets
* persisted but consume threw) recovers without
* double-decrementing inventory.
*/
inventoryConsumedAt?: string;
}[];
/**
* Audit log of every payment applied to this invoice
*/
payments?: {
id: string;
/**
* Outcome of this payment attempt.
* - succeeded: money was captured. Counts toward the parent invoice's
* `amountPaid` total.
* - failed: an auto-charge attempt was rejected (decline / SCA / no
* card). Kept on the audit log for visibility; does NOT count
* toward `amountPaid`.
*/
status: "succeeded" | "failed";
/**
* Amount applied, minor units
*/
amount: number;
paidAt: string;
/**
* Caller-supplied dedupe key, if one was provided
*/
idempotencyKey?: string;
/**
* Free-form note (payment method, upstream provider id, etc.)
*/
note?: string;
/**
* Stripe PaymentIntent id (`pi_…`) for Stripe-Connect payments
*/
externalId?: string;
/**
* Stripe Charge id (`ch_…`); refund operations key off this
*/
chargeId?: string;
/**
* Stripe error code on a failed attempt (e.g. `card_declined`, `authentication_required`)
*/
failureCode?: string;
/**
* Stripe customer-facing decline message on a failed attempt
*/
failureMessage?: string;
/**
* Refunds applied against this payment. Cumulative refunds are
* capped at `amount`; the host's true net = amount - sum(refunds).
*/
refunds?: {
id: string;
/**
* Refund amount, minor units of the parent invoice currency
*/
amount: number;
refundedAt: string;
idempotencyKey?: string;
/**
* Stripe reason: duplicate | fraudulent | requested_by_customer
*/
reason?: string;
/**
* Free-form host note (internal, not shown to customer)
*/
note?: string;
/**
* Stripe Refund id (`re_…`) for ops lookups
*/
externalRefundId?: string;
/**
* Pointer to the matching Ledger row that drives payouts
*/
ledgerEntryId?: string;
}[];
}[];
/**
* Sum of (qty * unitAmount), minor units
*/
subtotal?: number;
/**
* Sum of line taxAmount, minor units
*/
taxTotal?: number;
/**
* subtotal + taxTotal, minor units
*/
total?: number;
/**
* Sum of payments received, minor units
*/
amountPaid?: number;
/**
* total - amountPaid, clamped to >= 0
*/
amountDue?: number;
/**
* Invoice lifecycle status.
* - draft: not yet issued; freely editable
* - open: finalized and issued; awaiting payment
* - payment_failed: an off-session auto-charge attempt was rejected
* (decline / SCA / no card). The invoice is still owed; the host
* can retry the charge or the customer can pay manually.
* - paid: fully paid
* - void: cancelled before payment
*/
status?: "draft" | "open" | "payment_failed" | "paid" | "void";
/**
* When true, finalize attempts to off-session charge the customer's
* saved card immediately. On charge success the invoice transitions
* straight to `paid`; on failure to `payment_failed`. Default
* false — host has to opt in.
*/
autoCharge?: boolean;
notes?: string;
/**
* Snapshot of the host's registered tax-IDs at the time the invoice
* was issued. Rendered alongside the host's company info on the PDF
* and email body. Optional/empty when the host hasn't recorded any.
*/
taxIds?: {
/**
* Internal type code for the tax-ID (e.g. 'us_ein', 'ca_gst_hst', 'eu_vat').
* Display labels are derived at render time so the persisted invoice
* doesn't bake in a label that might be reworded.
*/
type: string;
/**
* The tax-ID number as the host has it registered
*/
value: string;
}[];
issuedAt?: string;
dueAt?: string;
paidAt?: string;
voidedAt?: string;
/**
* Set when generated by a billing Subscription
*/
subscriptionId?: string;
/**
* Set when accepted from a Quote
*/
quoteId?: string;
createdAt?: string;
updatedAt?: string;
};
}