The worked flow

From customer to closed loop.

Six requests: create a customer, invoice them, issue the invoice, record their payment, read the document, then read the ledger. A schema shows these as unrelated endpoints; this page shows that they compose into books.

Every request needs the Authorization: Token smly_live_… header from getting started; it is omitted below after the first appearance. The IDs are sample values — yours will differ.

1. Create the customer.

One write, one habit: an Idempotency-Key on every creating POST, so a timeout is a resend rather than a duplicate.

Request

curl -X POST https://app.summely.com/api/v1/customers \
  -H "Authorization: Token smly_live_k3J9x2Qw…" \
  -H "Idempotency-Key: 018f3a52-create-fern-frond" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Fern & Frond Landscaping",
    "email": "accounts@fernfrond.example",
    "payment_terms": "net_30"
  }'

Response — 201, abbreviated

{
  "id": "3f9d2c74-9d2a-4c1e-8f60-b21c7a4e5d10",
  "display_name": "Fern & Frond Landscaping",
  "payment_terms": "net_30",
  "balance": "0.0000",
  "version": 1
}

2. Invoice them.

Two lines: forty hours of design work, taxable under a 10% sales-tax rate created in the app, and pass-through permit fees, not taxable. Amounts go over the wire as strings. The account and tax-rate IDs come from your own GET /api/v1/accounts and GET /api/v1/tax-rates.

Request — POST /api/v1/invoices

{
  "customer": "3f9d2c74-9d2a-4c1e-8f60-b21c7a4e5d10",
  "lines": [
    {
      "description": "Landscape design, Maple Street project",
      "quantity": "40",
      "unit_price": "105.00",
      "income_account": "<id of 4100 Services>",
      "is_taxable": true,
      "tax_rate": "<id of your 10% sales-tax rate>"
    },
    {
      "description": "Permit fees, passed through",
      "quantity": "1",
      "unit_price": "200.00",
      "income_account": "<id of 4100 Services>",
      "is_taxable": false
    }
  ]
}

Response — 201, abbreviated

{
  "id": "c58f2b1d-7e3a-4a90-b1f2-6d5e4c3b2a19",
  "number": "INV-1042",
  "customer_name": "Fern & Frond Landscaping",
  "status": "draft",
  "issue_date": "2026-08-17",
  "due_date": "2026-09-16",
  "currency": "USD",
  "subtotal": "4400.0000",
  "tax_total": "420.0000",
  "total": "4820.0000",
  "amount_paid": "0.0000",
  "balance_due": "4820.0000",
  "version": 1
}

Three things the server decided for you: the number (INV-1042 — the org's next in sequence, because a client-side counter cannot survive two clients), the due date (thirty days out, from the customer's terms), and every total. Totals are never accepted from the client; they are computed from the lines, and they come back as strings.

3. Issue it.

A draft is a document, not a debt — it has put nothing into the ledger, and recording a payment against one is refused. Sending the invoice is what posts it to the books.

Request

curl -X POST \
  https://app.summely.com/api/v1/invoices/c58f2b1d-…/send

Response — abbreviated

{
  "number": "INV-1042",
  "status": "sent",
  "balance_due": "4820.0000",
  "version": 2
}

send issues the document without emailing anything; POST /api/v1/invoices/{id}/email does both. From this moment the receivable exists in the ledger — step 6 will show exactly what was written.

4. Record their payment.

A week later, the money arrives. Left unspecified, deposit_to_account defaults to Undeposited Funds — where money sits between arriving and being deposited at the bank.

Request — POST /api/v1/payments-received

{
  "customer": "3f9d2c74-9d2a-4c1e-8f60-b21c7a4e5d10",
  "payment_date": "2026-08-24",
  "amount": "4820.00",
  "method": "ach",
  "reference": "FF-2231",
  "applications": [
    {
      "invoice_id": "c58f2b1d-7e3a-4a90-b1f2-6d5e4c3b2a19",
      "amount": "4820.00"
    }
  ]
}

Response — 201, abbreviated

{
  "id": "e1a9c3d2-5b4f-4e8a-a710-9c8b7a6d5e4f",
  "number": "PMT-1007",
  "payment_date": "2026-08-24",
  "amount": "4820.0000",
  "unapplied_amount": "0.0000",
  "applications": [
    {
      "invoice_number": "INV-1042",
      "amount": "4820.0000",
      "applied_date": "2026-08-24"
    }
  ],
  "version": 1
}

5. Read the invoice again.

Nobody told the invoice it was paid — the payment application did. The document tells you.

Request

curl https://app.summely.com/api/v1/invoices/c58f2b1d-…

Response — abbreviated

{
  "number": "INV-1042",
  "status": "paid",
  "amount_paid": "4820.0000",
  "balance_due": "0.0000",
  "days_overdue": 0
}

6. The payoff: read the ledger.

Everything above was documents. This is the books those documents produced — filter the journal by source, and the entry that issuing the invoice posted comes back balanced.

Request

curl "https://app.summely.com/api/v1/journal-entries\
?source_type=invoice&source_id=c58f2b1d-…"

Response — abbreviated

{
  "results": [
    {
      "entry_number": "JE-000214",
      "txn_date": "2026-08-17",
      "status": "posted",
      "source_type": "invoice",
      "total_debit": "4820.0000",
      "total_credit": "4820.0000",
      "lines": [
        { "account_number": "1100",
          "account_name": "Accounts Receivable",
          "debit": "4820.0000", "credit": "0.0000" },
        { "account_number": "4100",
          "account_name": "Services",
          "debit": "0.0000", "credit": "4400.0000" },
        { "account_number": "2200",
          "account_name": "Sales Tax Payable",
          "debit": "0.0000", "credit": "420.0000" }
      ]
    }
  ]
}

The same query with source_type=payment_received and the payment's ID returns the second entry. Side by side, they are the whole story:

Journal entry — invoice INV-1042
Account Debit Credit
Accounts receivable 4,820.00
Services 4,400.00
Sales tax payable 420.00
Balanced 4,820.00 4,820.00
Journal entry — payment PMT-1007
Account Debit Credit
Undeposited funds 4,820.00
Accounts receivable 4,820.00
Balanced 4,820.00 4,820.00

What this flow teaches.

Every write was idempotent, so any of them could have timed out and been resent without harm. Nothing was ever mutated: the payment did not edit the invoice's ledger entries, it posted its own, and the invoice's status changed because the ledger did. And everything the flow created is on screen in the app right now — the app is a client of the same API, with no private endpoints behind it.

The rest is reference.

Error codes, rate limits, idempotency, concurrency and the versioning promise — one page, built for eleven at night.