{"openapi":"3.1.0","info":{"title":"POST /api/v1/invoices/bulk","version":"1.0.0","description":"Bulk create invoices"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/invoices/bulk":{"post":{"tags":["invoices"],"summary":"Bulk create invoices","description":"Create multiple invoices from CSV data in a single request.","operationId":"create_bulk_invoices_api_v1_invoices_bulk_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-business-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Business-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceBulkRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_InvoiceBulkCreateResponse_"}}}},"400":{"description":"Bad Request - Invalid input","content":{"application/json":{"example":{"detail":"Invalid request parameters"}}}},"401":{"description":"Unauthorized - Authentication required","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Forbidden - Insufficient permissions","content":{"application/json":{"example":{"detail":"Not enough permissions"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InvoiceBulkCreateResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/InvoiceResponse"},"type":"array","title":"Items","description":"List of items"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total number of items (null when not calculated for performance)"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit","description":"Pagination limit"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Cursor for next page"},"prev_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prev Cursor","description":"Cursor for previous page"},"has_next":{"type":"boolean","title":"Has Next","description":"Whether there are more items"},"has_prev":{"type":"boolean","title":"Has Prev","description":"Whether there are previous items"},"total_requested":{"type":"integer","title":"Total Requested","description":"Invoices parsed out of the CSV (created + failed + skipped)."},"total_created":{"type":"integer","title":"Total Created","description":"Invoices actually created."},"total_failed":{"type":"integer","title":"Total Failed","description":"Rows that could not be imported."},"total_duplicates":{"type":"integer","title":"Total Duplicates","description":"Rows skipped because they were already imported."},"failed":{"items":{"$ref":"#/components/schemas/InvoiceBulkRowError"},"type":"array","title":"Failed","description":"Per-row failures with reasons."},"duplicates":{"items":{"$ref":"#/components/schemas/InvoiceBulkDuplicate"},"type":"array","title":"Duplicates","description":"Per-row duplicates that were skipped."},"partial":{"type":"boolean","title":"Partial","description":"True when the import created some invoices but not all of them (some rows failed or were skipped as duplicates)."},"success":{"type":"boolean","title":"Success","description":"True when no row failed validation or creation."}},"type":"object","required":["items","has_next","has_prev","total_requested","total_created","total_failed","total_duplicates","partial","success"],"title":"InvoiceBulkCreateResponse","description":"Outcome of a bulk CSV import.\n\nImports are partial by design: valid rows are created even when others\nfail validation or are skipped as duplicates. ``items``/``total`` carry\nthe created invoices for backwards compatibility with the plain list\nresponse this endpoint used to return."},"InvoiceBulkDuplicate":{"properties":{"index":{"type":"integer","title":"Index","description":"Zero-based position of the invoice within the file."},"row":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row","description":"1-based CSV line number (the header is line 1)."},"customer_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Email","description":"customer_email from the skipped row."},"invoice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Id","description":"Existing invoice this row duplicates. Null when the duplicate is a repeat of an earlier row in the same file."},"number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Number","description":"Invoice number of the existing invoice, when it has one."},"reason":{"type":"string","title":"Reason","description":"Why the row was treated as a duplicate."}},"additionalProperties":true,"type":"object","required":["index","reason"],"title":"InvoiceBulkDuplicate","description":"A CSV row skipped because a prior import already created it."},"InvoiceBulkRequest":{"properties":{"s3_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"S3 Key","description":"S3 key of the uploaded CSV file containing invoice data."},"finalize":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Finalize","description":"If true, immediately finalize all created invoices.","default":false},"allow_duplicates":{"type":"boolean","title":"Allow Duplicates","description":"By default a row that matches an invoice a previous import already created (same customer, due date, total, description and line items) is skipped and reported under `duplicates`, so uploading the same file twice is safe. Set true to import those rows anyway — they are created without a fingerprint and will not be deduplicated against on future uploads.","default":false},"historical":{"type":"boolean","title":"Historical","description":"The file holds invoices that were already issued elsewhere — a books migration rather than new billing. Every row is written locally and none reach Stripe, even if the business has an account connected, so nothing can be sent to or charged to a customer. Past due dates are accepted, which they otherwise are not. Use the `invoice_date` and `invoice_number` columns to keep each invoice's original date and number.","default":false}},"additionalProperties":true,"type":"object","title":"InvoiceBulkRequest","description":"Request schema for bulk invoice creation via CSV upload.\n\nUpload a CSV file to S3 first, then provide the s3_key here.\n\nCSV columns expected:\n- due_date: Payment due date in YYYY-MM-DD format (marks start of new invoice)\n- customer_email: Email of the customer (must exist in the system)\n- description: Optional invoice-level description/notes\n- variant_sku: SKU of the variant to add (leave blank for custom line items)\n- quantity: Quantity for the line item\n- custom_line_item_description: Description for custom line items (when variant_sku is blank)\n- custom_line_item_amount: Total for the line, in dollars\n- custom_line_item_unit_price: Price per unit, in dollars, multiplied by quantity.\n  Supply this or custom_line_item_amount; if both, they must agree.\n- invoice_note: Optional invoice description/notes\n\nOptional columns, read from each invoice's first row:\n- invoice_number: Keep the number the invoice already has (else auto-assigned)\n- invoice_date: The date the invoice was issued, i.e. what it is reported under\n- project_code / project_name: Attribute the invoice to a project (job)\n\nNote: an invoice spans consecutive rows. Any row carrying BOTH due_date and\ncustomer_email starts a new invoice; rows that leave both blank add line\nitems to the invoice above them. Repeating due_date + customer_email on a\ncontinuation row therefore does not extend that invoice — it starts another\none.","example":{"allow_duplicates":false,"finalize":false,"historical":false,"s3_key":"uploads/invoices/batch-2024-01.csv"}},"InvoiceBulkRowError":{"properties":{"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index","description":"Zero-based position of the invoice within the file, or null for errors that aren't tied to a parsed invoice."},"row":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row","description":"1-based CSV line number (the header is line 1)."},"customer_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Email","description":"customer_email from the offending row, when known."},"error":{"type":"string","title":"Error","description":"Human-readable reason the row failed."}},"additionalProperties":true,"type":"object","required":["error"],"title":"InvoiceBulkRowError","description":"A CSV row (or invoice block) that could not be imported."},"InvoicePaymentTransactionSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Transaction UUID."},"amount":{"type":"integer","title":"Amount","description":"Transaction amount in cents. Positive = deposit, negative = withdrawal."},"currency":{"type":"string","title":"Currency","description":"Currency code (USD, CAD, AUD, EUR, or GBP).","default":"USD"},"datetime":{"type":"string","format":"date","title":"Datetime","description":"The date the transaction occurred."},"counterparty":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Counterparty","description":"Clean counterparty/merchant name."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Transaction memo/description."},"ledger_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ledger Id","description":"Bank account (ledger) UUID."}},"type":"object","required":["id","amount","datetime"],"title":"InvoicePaymentTransactionSummary","description":"Lightweight transaction summary embedded in invoice payment responses.\n\nContains the key fields needed to display the linked bank transaction\nwithout pulling the full TransactionResponse (journal entries, threads, etc.)."},"InvoiceResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Invoice UUID."},"business_id":{"type":"string","title":"Business Id","description":"Business UUID."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp."},"status":{"type":"string","title":"Status","description":"Status: draft, open, partially_paid, manual_paid, stripe_paid, void."},"currency":{"type":"string","title":"Currency","description":"Currency for the invoice (USD, CAD, AUD, EUR, or GBP).","default":"USD"},"stripe_invoice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Invoice Id","description":"Stripe invoice ID if synced."},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total amount in cents."},"total_paid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Paid","description":"Running total of payments received in cents."},"balance_due":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Balance Due","description":"Remaining balance in cents (total - total_paid)."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Invoice description/notes."},"customer_id":{"type":"string","title":"Customer Id","description":"Customer UUID."},"customer_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Name","description":"Customer name (resolved)."},"customer_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Email","description":"Customer email (resolved)."},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date","description":"Due date (YYYY-MM-DD)."},"line_items":{"anyOf":[{"items":{"$ref":"#/components/schemas/LineItemResponse"},"type":"array"},{"type":"null"}],"title":"Line Items","description":"Invoice line items."},"fulfillment_location_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fulfillment Location Id","description":"Default warehouse used for inventory deductions."},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"UUID of the linked project (job), if any."},"project_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Name","description":"Name of the linked project (resolved), if any."},"project_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Code","description":"Short code of the linked project (resolved), if any."},"contract_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contract Id","description":"UUID of the contract this invoice bills against, if any."},"revenue_treatment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revenue Treatment","description":"Manual revenue-tracking label ('earned' / 'deferred' / null). Informational only — posts no journal entry."},"pdf_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdf Url","description":"URL to download invoice PDF."},"number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Number","description":"Invoice number (assigned on finalize)."},"delivered":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Delivered","description":"Whether invoice was delivered."},"delivered_on":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Delivered On","description":"Delivery timestamp."},"hosted_invoice_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hosted Invoice Url","description":"URL for customer payment portal."},"paid_on":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid On","description":"Payment timestamp."},"journal_entries":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Journal Entries","description":"Associated accounting journal entries.","default":[]},"payments":{"anyOf":[{"items":{"$ref":"#/components/schemas/app__api__v1__schemas__invoices__invoice__InvoicePaymentResponse"},"type":"array"},{"type":"null"}],"title":"Payments","description":"Payment records linking bank transactions to this invoice.","default":[]},"recurring_template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recurring Template Id","description":"ID of recurring template if invoice was generated from one, or if one was created."},"stripe_settlement":{"anyOf":[{"$ref":"#/components/schemas/StripeSettlementInfo"},{"type":"null"}],"description":"Stripe payout and bank deposit linkage for stripe_paid invoices."}},"additionalProperties":true,"type":"object","required":["id","business_id","created_at","updated_at","status","customer_id"],"title":"InvoiceResponse","description":"Response schema for an invoice.\n\nContains all invoice details including customer info, line items,\npayment status, and associated accounting entries.","example":{"business_id":"019ab37c-bus1-7000-8000-000000000001","created_at":"2024-01-15T10:30:00Z","customer_email":"billing@acme.com","customer_id":"019ab37c-cust-7000-8000-000000000001","customer_name":"Acme Corporation","delivered":true,"delivered_on":"2024-01-16T09:00:00Z","description":"January services","due_date":"2024-02-15","id":"019ab37c-inv1-7000-8000-000000000001","line_items":[{"amount":75000,"description":"Widget Pro","quantity":3,"unit_price":25000,"variant_id":"019ab37c-var1-7000-8000-000000000001","variant_name":"Widget Pro - Large"},{"amount":2500,"description":"Rush delivery fee","quantity":1,"variant_id":"custom"}],"number":"INV-2024-0001","status":"pending","total":77500,"updated_at":"2024-01-15T10:30:00Z"}},"LineItemResponse":{"properties":{"variant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Variant Id","description":"Product variant UUID or 'custom'."},"quantity":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quantity","description":"Quantity ordered."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Line item description."},"amount":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Amount","description":"Total line amount in cents."},"variant_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Variant Name","description":"Product variant name (resolved)."},"unit_price":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unit Price","description":"Price per unit in cents."},"catalog_item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Catalog Item Id","description":"Books catalog item UUID this line is based on (if any)."},"location_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Id","description":"Warehouse UUID this line was deducted from (if set)."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Free-text note for this line item, if any."},"deferred":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Deferred","description":"Whether this line's revenue is tagged for deferral. See `LineItemRequest.deferred`.","default":false}},"additionalProperties":true,"type":"object","title":"LineItemResponse","description":"Line item in an invoice response.\n\nContains the resolved product/variant information along with pricing."},"StripePayoutBankTransactionSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Bank transaction UUID."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Transaction memo/description."},"datetime":{"type":"string","format":"date","title":"Datetime","description":"The date the deposit occurred."},"amount":{"type":"integer","title":"Amount","description":"Deposit amount in cents (positive)."}},"type":"object","required":["id","datetime","amount"],"title":"StripePayoutBankTransactionSummary","description":"Lightweight bank deposit linked to a Stripe payout."},"StripePayoutInvoiceBreakdownItem":{"properties":{"label":{"type":"string","title":"Label","description":"Customer or invoice label from Stripe."},"amount_cents":{"type":"integer","title":"Amount Cents","description":"Charge amount in cents."},"stripe_charge_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Charge Id","description":"Stripe charge ID (ch_…), when available."},"dayzero_invoice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dayzero Invoice Id","description":"Matched DayZero invoice UUID, when resolved."},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number","description":"Invoice number from metadata or charge description."}},"type":"object","required":["label","amount_cents"],"title":"StripePayoutInvoiceBreakdownItem","description":"One settled charge row included in a Stripe payout."},"StripePayoutSummary":{"properties":{"stripe_payout_id":{"type":"string","title":"Stripe Payout Id","description":"Stripe payout ID (e.g. po_…)."},"status":{"type":"string","title":"Status","description":"Stripe payout status (e.g. paid, pending)."},"amount_cents":{"type":"integer","title":"Amount Cents","description":"Net payout amount deposited in cents."},"gross_amount_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Gross Amount Cents","description":"Gross charge total in cents before Stripe fees, when computed."},"fee_amount_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fee Amount Cents","description":"Total Stripe fees in cents from balance transactions, when computed."},"stripe_dashboard_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Dashboard Url","description":"Stripe Dashboard URL for this payout, when account context is known."},"payment_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Summary","description":"Human-readable customer/invoice breakdown from balance transactions."},"invoice_breakdown":{"anyOf":[{"items":{"$ref":"#/components/schemas/StripePayoutInvoiceBreakdownItem"},"type":"array"},{"type":"null"}],"title":"Invoice Breakdown","description":"Structured per-charge invoice rows for expandable payout UI."},"arrival_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Arrival Date","description":"Expected bank arrival date from Stripe."},"bank_deposit_match_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bank Deposit Match Id","description":"Matched Plaid bank deposit transaction id, if linked."},"journal_entry_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Journal Entry Id","description":"Payout journal entry UUID, if created."},"is_matched":{"type":"boolean","title":"Is Matched","description":"True when bank_deposit_match_id is set.","default":false},"bank_transaction":{"anyOf":[{"$ref":"#/components/schemas/StripePayoutBankTransactionSummary"},{"type":"null"}],"description":"Matched bank deposit summary when linked."}},"type":"object","required":["stripe_payout_id","status","amount_cents"],"title":"StripePayoutSummary","description":"Stripe payout metadata for reconciliation audit trails."},"StripeSettlementInfo":{"properties":{"payout":{"$ref":"#/components/schemas/StripePayoutSummary","description":"Stripe payout that settled this invoice's card payment."}},"type":"object","required":["payout"],"title":"StripeSettlementInfo","description":"Stripe settlement chain for a stripe_paid invoice (payout → bank deposit)."},"SuccessEnvelope_InvoiceBulkCreateResponse_":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"data":{"anyOf":[{"$ref":"#/components/schemas/InvoiceBulkCreateResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[InvoiceBulkCreateResponse]"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"app__api__v1__schemas__invoices__invoice__InvoicePaymentResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Payment record UUID."},"invoice_id":{"type":"string","title":"Invoice Id","description":"UUID of the invoice being paid."},"transaction_id":{"type":"string","title":"Transaction Id","description":"UUID of the bank transaction."},"amount":{"type":"integer","title":"Amount","description":"Allocated payment amount in cents."},"currency":{"type":"string","title":"Currency","description":"Currency (USD, CAD, AUD, EUR, or GBP).","default":"USD"},"paid_on":{"type":"string","format":"date-time","title":"Paid On","description":"Effective payment date."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp."},"business_id":{"type":"string","title":"Business Id","description":"Business UUID."},"transaction":{"anyOf":[{"$ref":"#/components/schemas/InvoicePaymentTransactionSummary"},{"type":"null"}],"description":"Linked bank transaction summary. Included when listing payments."}},"type":"object","required":["id","invoice_id","transaction_id","amount","paid_on","created_at","updated_at","business_id"],"title":"InvoicePaymentResponse","description":"Response schema for an invoice payment record.\n\nRepresents a payment made against an invoice, linking to the bank\ntransaction with a specific allocated amount.","example":{"amount":12000,"business_id":"019ab37c-bus1-7000-8000-000000000001","created_at":"2026-02-10T12:00:00Z","currency":"USD","id":"019ab37c-pay1-7000-8000-000000000001","invoice_id":"019ab37c-inv1-7000-8000-000000000001","paid_on":"2026-02-10T00:00:00Z","transaction":{"amount":12000,"counterparty":"Acme Corp","currency":"USD","datetime":"2026-02-10T00:00:00Z","description":"Invoice payment","id":"019ab37c-txn1-7000-8000-000000000001","ledger_id":"019ab37c-led1-7000-8000-000000000001"},"transaction_id":"019ab37c-txn1-7000-8000-000000000001","updated_at":"2026-02-10T12:00:00Z"}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Token","description":"API token authentication. Format: `Bearer dz_...`"}}},"security":[{"BearerAuth":[]}]}