FeaturesComparePricingAPIFAQLog inStart free trial

API reference

A small JSON REST API for creating and emailing invoices. Base URL: https://cbdcoffee.store

AuthenticationInvoice objectEndpointsBulkE-signatureLimitsErrorsCode samples

Authentication

Create an API key in your dashboard (API keys tab). Send it on every request. Keys start with inv_live_ and are shown only once, so store them in an environment variable or secrets manager and never in front-end code.

Authorization: Bearer inv_live_xxxxxxxxxxxxxxxx
Content-Type: application/json

The invoice object

FieldTypeNotes
idstringAssigned by the server, e.g. INV-1001
clientstringRequired. Client or company name (max 200 chars)
emailstringRequired. Where the invoice is sent
itemsarrayRequired. 1 to 100 items: { desc, qty, price }
taxnumberTax percentage applied to the subtotal (0 to 100)
currencystringUSD, EUR, GBP or INR (default USD)
duestringDue date, YYYY-MM-DD
notesstringFree text printed on the invoice (max 1000 chars)
statusstringdraft, sent or paid
created / sentAtstringISO 8601 timestamps
subtotal, taxAmount, totalnumberCalculated by the server, read-only
{
  "id": "INV-1001", "client": "Acme Ltd", "email": "billing@acme.com",
  "items": [{ "desc": "Web design", "qty": 1, "price": 1200 }],
  "tax": 10, "currency": "USD", "due": "2026-12-01", "notes": "",
  "status": "draft", "created": "2026-11-09T10:00:00.000Z",
  "subtotal": 1200, "taxAmount": 120, "total": 1320
}

Endpoints

GET /v1/invoices

List your invoices, newest first. Optional ?status=draft|sent|paid.

POST /v1/invoices

Create a draft invoice. Returns 201 and the invoice. Requires an active trial or plan.

GET /v1/invoices/:id

Fetch one invoice.

PATCH /v1/invoices/:id

Update any field, or set status to paid. Send only the fields you want to change.

DELETE /v1/invoices/:id

Delete an invoice. Returns 204 with no body.

POST /v1/invoices/:id/send

Email the invoice and PDF to the client and mark it as sent. Counts as one invoice against your quota. Requires a verified email address.

{ "message": "Invoice emailed to billing@acme.com",
  "quota": { "period": "day", "limit": 100, "used": 7 },
  "invoice": { "id": "INV-1001", "status": "sent", "...": "..." } }

GET /v1/stats

Totals for outstanding and paid invoices, plus your current quota.

Bulk sending

Send the CSV text as a JSON string. Maximum 5,000 rows and 5 MB per upload. Columns: client, email, description, qty, price (required) and tax, currency, due, notes, group. Download the template.

POST /v1/bulk/preview

Body { "csv": "..." }. Validates without creating anything. Returns row and invoice counts, up to 50 errors with line numbers, a sample, and your remaining quota.

POST /v1/bulk

Body { "csv": "...", "send": true, "skipInvalid": false }. Creates the invoices and queues them for sending (send: false creates drafts only). Returns 202 and a batchId. Fails with 422 if rows have errors and skipInvalid is not true, 429 if the file exceeds your remaining quota, and 409 if a batch is already sending.

GET /v1/bulk and GET /v1/bulk/:id

Batch progress: total, sent, failed, pending, status. The single-batch endpoint also lists failures with the reason.

E-signature

POST /v1/docsign/upload?title=Contract

Upload a PDF as the raw request body (Content-Type: application/pdf, up to 10 MB and 100 pages; password-protected PDFs are refused). Returns 201 with the draft id, pages and page sizes.

POST /v1/docsign/:id/send

Place fields and send. Body: signers (array of {name, email}, 1 to 10), fields (array of {signer, type, page, x, y, w, h, required} where signer is the index in signers, type is signature, date, name or text, page starts at 0 and x, y, w, h are fractions from 0 to 1 of the page, measured from the top-left), optional order (parallel or sequential), message, title and expires_days. Every signer needs at least one signature field. Each signer counts as one send.

GET /v1/docsign, GET /v1/docsign/:id, GET /v1/docsign/:id/signed, POST /v1/docsign/:id/cancel, DELETE /v1/docsign/:id

List, inspect, download the completed PDF (with audit page), cancel a pending request, or delete a document.

POST /v1/esign

Emails a signature request. Body: title, body (the document text, 10 to 20,000 characters), signer_name, signer_email, optional message and expires_days (7, 14 or 30). Counts as one send. Returns 201.

GET /v1/esign, GET /v1/esign/:id

List requests or fetch one. Status is pending, signed, declined, cancelled or expired.

POST /v1/esign/:id/cancel

Cancel a pending request.

GET /v1/esign/:id/pdf

Download the PDF with the signature and audit trail (once signed).

Rate limits and quotas

Requests per minute depend on your plan (Free trial: 60, up to 1,000 on Business). Each response includes X-RateLimit-Limit and X-RateLimit-Remaining. When exceeded you get 429 and a Retry-After header in seconds.

Sending quota is counted per email sent: each invoice (single or bulk) and each e-signature request. A successful send returns X-Quota-Limit and X-Quota-Remaining. Daily quotas reset at 00:00 UTC.

PlanPriceSends (invoices + e-sign requests)API keysAPI requests / minAI messages / day
Free trial (7 days)$0100 per day16030
Starter$10 / month500 per day360100
Growth$25 / month2,000 per day10300300
Business$50 / month10,000 per day501,0001,000

Errors

StatusMeaningWhat to do
400Body is not valid JSONFix the request body
401Missing or invalid API keyCheck the Authorization header; the key may be revoked
402Trial or plan has endedSubscribe to continue creating and sending. Reading and deleting still work
403Email not verified, or key limit reachedVerify your email, or revoke an unused key
404Invoice not foundCheck the id
422Validation failedThe error field explains what to correct
429Rate limit or invoice quota reachedWait for Retry-After, or upgrade for a higher limit
502Email delivery failedRetry later. Your quota is not charged for failed sends
{ "error": "Invoice limit reached (100 per day).", "quota": { "period": "day", "limit": 100, "used": 100 } }

Code samples

cURL

curl -X POST https://cbdcoffee.store/v1/invoices \
  -H "Authorization: Bearer $INVOICE_API_KEY" -H "Content-Type: application/json" \
  -d '{"client":"Acme Ltd","email":"billing@acme.com","tax":10,"items":[{"desc":"Web design","qty":1,"price":1200}]}'

curl -X POST https://cbdcoffee.store/v1/invoices/INV-1001/send -H "Authorization: Bearer $INVOICE_API_KEY"

JavaScript (Node 18+)

const base = "https://cbdcoffee.store";
const headers = { Authorization: "Bearer " + process.env.INVOICE_API_KEY, "Content-Type": "application/json" };

const res = await fetch(base + "/v1/invoices", { method: "POST", headers,
  body: JSON.stringify({ client: "Acme Ltd", email: "billing@acme.com", items: [{ desc: "Web design", qty: 1, price: 1200 }] }) });
const invoice = await res.json();

const sent = await fetch(base + "/v1/invoices/" + invoice.id + "/send", { method: "POST", headers });
if (sent.status === 429) console.log("Limit reached, retry after", sent.headers.get("Retry-After"));

Python

import os, requests
base = "https://cbdcoffee.store"
h = {"Authorization": "Bearer " + os.environ["INVOICE_API_KEY"]}
inv = requests.post(base + "/v1/invoices", headers=h, json={
    "client": "Acme Ltd", "email": "billing@acme.com",
    "items": [{"desc": "Web design", "qty": 1, "price": 1200}]}).json()
requests.post(base + "/v1/invoices/" + inv["id"] + "/send", headers=h)
Not available yet: webhooks (for example when a document is signed) and idempotency keys. To avoid duplicates when retrying a create call, store the returned id on your side.