SaaS Billing to Accounting: Subscriptions, Invoices, Revenue

Your customers subscribe to your product. Your billing system knows who pays what, when. What it usually doesn't produce is books: receivables, revenue, deferred revenue, a trial balance an accountant will sign off on. This guide closes that gap.

By the end you'll have: a subscription plan modeled as an item with its revenue account attached, a recurring invoice that re-issues itself on a schedule, a payment recorded against it, a manual journal handling deferred revenue for an annual prepay, and the journal list to prove all of it. The billing side stays yours — Stripe, your own gateway, whatever — this is the accounting side it feeds.

One thing to be clear about up front: revenue recognition is not automated here. When an invoice posts, revenue posts — that's accrual accounting, and for monthly subscriptions billed monthly it's also correct recognition. For annual prepay, where cash arrives twelve months ahead of the service, you model the deferral yourself with manual journals against a deferred-revenue liability account. Step 4 shows exactly that, because pretending it's magic is how books end up wrong.

Prerequisites

You need a company, a customer, and a chart of accounts. If you don't have those yet, run the 20-minute guide first — it covers sandbox keys, company creation, the chart of accounts, and customer setup. This guide picks up from there and assumes the same header setup:

Shell
export ACCESS_TOKEN="<from the credential exchange>"
export BASE="https://api-sandbox.paprel.com"
export AUTH="authorization: Bearer $ACCESS_TOKEN"
export CT="content-type: application/json"

First time? The five-minute setup — token exchange and request signing — is on the Authentication page. The curl examples below show the auth header only; write requests also require an x-data-signature header, which the SDK computes for you.

You'll also want two accounts in the chart for this flow: a Subscription Revenue income account, and a Deferred Revenue liability account (for step 4). Create them the same way as in step 2 of the 20-minute guide.


Step 1 — Model the plan as an item

A subscription plan is an item that knows where its revenue belongs:

Shell
curl -X POST $BASE/v1/items \
  -H "$AUTH" -H "$CT" \
  -d '{
    "item_name": "Pro plan — monthly",
    "item_type": "service",
    "sku": "PLAN-PRO-M",
    "sales_price": "99.00",
    "sales_currency": "USD",
    "sales_account_id": "<Subscription Revenue account id>",
    "sales_description": "Pro plan, monthly subscription"
  }'

Endpoint reference: POST /v1/items

If the customer in this walkthrough isn't created yet, POST /v1/clients from the 20-minute guide gets you a people_id.

Behind the scenes: the sales_account_id mapping is the whole trick. Every invoice that bills this plan — including every future invoice the recurrence generates without you in the loop — posts revenue to Subscription Revenue. The accounting decision is made once, on the item, not re-made per invoice by application code.

Step 2 — Create the recurring subscription invoice

This is one invoice that re-issues itself:

Shell
curl -X POST $BASE/v1/invoices \
  -H "$AUTH" -H "$CT" \
  -d '{
    "people_id": "<customer people_id>",
    "currency": "USD",
    "document_issue_date": "2026-06-12",
    "document_due_date": "2026-06-26",
    "is_recurring": true,
    "recurrence_days": 30,
    "recurring_start_date": "2026-06-12",
    "recurring_end_date": "2027-06-12",
    "recurring_delivery_instruction": "send",
    "is_partial_payment_allowed": false,
    "items": [
      {
        "item_id": "<item id from step 1>",
        "item_name": "Pro plan — monthly",
        "item_quantity": "1",
        "item_cost": "99.00"
      }
    ]
  }'

Endpoint reference: POST /v1/invoices

The recurring fields: recurrence_days is the interval in days between issues, recurring_start_date / recurring_end_date bound the schedule, and recurring_delivery_instruction controls what happens when each new invoice is generated — whether it's delivered to the customer automatically or left for you to send. If you take card payments through a configured gateway, set payment_gateway_id so the invoice carries a pay link; if you allow installments, is_partial_payment_allowed and min_payment_amount are there too.

Keep the returned anchor_id — it's the stable identifier across edits, and the payment in step 3 takes it as the path parameter.

Behind the scenes: posting the first invoice generates a balanced journal through the item's account mapping:

AccountDebitCredit
Accounts Receivable99.00
Subscription Revenue99.00

Every recurrence posts the same shape on its own schedule. For a monthly plan billed monthly, this is correct revenue recognition: the month of service and the month of billing coincide, so accrual posting at invoice time matches when revenue is earned. No deferral needed — that's the annual-prepay case, in step 4.

Step 3 — Record the payment

Your gateway charged the card. Tell the books:

Shell
curl -X POST $BASE/v1/invoices/<anchor_id>/payment \
  -H "$AUTH" -H "$CT" \
  -d '{
    "payment_amount": "99.00",
    "payment_date": "2026-06-13",
    "payment_mode": "…",
    "deposit_account": "<bank/cash account id>",
    "reference_number": "ch_3PqK…"
  }'

Endpoint reference: POST /v1/invoices/:anchor_id/payment

Put the gateway's charge id in reference_number — when someone reconciles the bank feed against the books in six months, that string is the thread they pull. If the gateway took its fee before settlement, bank_charge records it on the same call.

Behind the scenes:

AccountDebitCredit
Cash · Operating99.00
Accounts Receivable99.00

Receivables for this customer drop, cash rises, revenue is untouched — it was earned in step 2. The invoice flips to paid, and the payment shows up in the document's activity history with everything else.

Step 4 — Annual prepay: model deferred revenue with manual journals

Now the case billing systems get wrong. A customer prepays $1,188 for a year. The invoice posts the full amount to revenue on day one — but only one month of it is earned. The other eleven months are a liability: you owe the customer service.

Paprel does not generate a recognition schedule for you. You model it: reclass the unearned portion to a Deferred Revenue liability account, then recognize one month at a time with a manual journal. First, the reclass right after the annual invoice posts:

Shell
curl -X POST $BASE/v1/accounting/journals \
  -H "$AUTH" -H "$CT" \
  -d '{
    "date": "2026-06-12",
    "currency": "USD",
    "is_manual": true,
    "reference": "DEFREV-PRO-ANNUAL-2026-06",
    "description": "Defer 11 months of annual Pro prepay",
    "lines": [
      {
        "account_id": "<Subscription Revenue account id>",
        "debit": "1089.00",
        "description": "Unearned portion of annual prepay"
      },
      {
        "account_id": "<Deferred Revenue account id>",
        "credit": "1089.00",
        "description": "Unearned portion of annual prepay"
      }
    ]
  }'

Endpoint reference: POST /v1/accounting/journals

Then, each month, the recognition entry — this is the one your finance reviewer will ask about:

AccountDebitCredit
Deferred Revenue99.00
Subscription Revenue99.00

Same endpoint, debit on the liability, credit on revenue, a reference that names the contract and the month. Run it from your own scheduler — twelve entries for a twelve-month contract, eleven after the reclass month. Lines must balance; the ledger rejects unbalanced journals, manual or not.

Behind the scenes: after the reclass, the balance sheet shows $1,089 in Deferred Revenue — a true statement that you owe eleven months of service. Each monthly recognition entry moves $99 from the liability to the P&L in the month it's earned. Your MRR-to-GAAP story is now just journal rows anyone can audit, not a spreadsheet on someone's laptop.

Step 5 — Read it all back

Shell
curl "$BASE/v1/accounting/journals?date_range=…&manual=true" -H "$AUTH"

Endpoint reference: GET /v1/accounting/journals

The journal list supports filtering by date_range, account, reference, and manual — so "show me every deferred-revenue entry for June" is one query against your DEFREV- reference convention, and dropping manual shows the document-generated postings from steps 2 and 3 alongside them. The P&L shows recognized revenue only; the balance sheet carries the deferred liability; the trial balance ties, because every posting above was balanced at write time.


What you just built

A subscription that bills itself, posts its own receivable and revenue journals, settles cleanly against cash, and — where recognition genuinely needs judgment — a deferral pattern that's explicit, auditable, and entirely under your control. Your first journal posts within minutes of the first invoice; the rest is the recurrence doing its job.

Where to go next:

  • API reference — full schemas for invoices, payments, and journals
  • Pricing — each invoice and each payment is one op; a monthly subscriber is roughly two ops a month, plus one per manual journal
  • Build embedded accounting in 20 minutes — the company, chart-of-accounts, and customer setup this guide builds on
Evaluate Paprel

Build in sandbox, launch with a production trial

Use sandbox for developer testing with no billing. When you are ready for real workflows, start production on a monthly plan with a 14-day free trial.

API-First Delivery
Audit-Ready Controls
Sandbox And Guided Rollout
We use cookies to improve your experience. Manage preferences or accept all.