Connect Your Point of Sale (POS) to Pennylane
Use this guide to connect your Point of Sale (POS) system to Pennylane and automatically post your daily sales reports (Ticket Z) as balanced accounting entries.
Goal
This tutorial shows how to integrate a POS with Pennylane to send daily sales summaries as accounting entries so your revenue, VAT, and payment data are automatically synchronized.
You will learn how to:
- Verify authentication and permissions
- Identify or create a POS journal
- Map your sales, VAT, and payment accounts
- Post daily entries to Pennylane
✅ End result:
Your POS automatically creates daily sales entries in Pennylane — balanced, traceable, and ready for accounting.
Who is this for?
Integration partners or software vendors building a POS > Pennylane integration.
Authentication
Partner integrations must use OAuth 2.0 for authentication.
In sandbox environments, you can test this flow with a Company API token, but OAuth 2.0 is required for production integrations published on the Marketplace.
Before You Get Started
Required scopes (Company API v2)
| Scope | Features |
|---|---|
ledger | create entries, list journals & ledger accounts |
file_attachments:all | optional – attach Ticket Z PDFs |
You will need:
- A Pennylane company selected through OAuth consent
- Your OAuth access token
- Company-specific IDs (journals, ledger accounts) you’ll fetch below
IDs are company-specific
Journal and ledger account IDs differ per company. Store them per tenant in your POS.
Step 1 | (Optional) Verify Authentication
Before posting sales entries, confirm that your token and environment are correctly set up.
curl https://app.pennylane.com/api/external/v2/me \
-H "Authorization: Bearer <ACCESS_TOKEN>"✅ Expected response: 200 OK - authentication successful.
Tip: Run this check when configuring your POS integration for the first time, or when switching between sandbox and production environments.
Step 2 | Identify (or Creating) the POS Journal
List Journals (Let the User Choose)
curl --request GET \
--url https://app.pennylane.com/api/external/v2/journals \
-H "Authorization: Bearer <ACCESS_TOKEN>"Typical codes
| Code | Journal |
|---|---|
VT | Sales journal |
HA | Purchases journal |
BQ | Bank journal |
OD | General journal |
Result
Store the chosen
journal_idand reuse it for all POS entries.
Create a Dedicated POS Journal (if Missing)
curl --request POST \
--url https://app.pennylane.com/api/external/v2/journals \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"code": "POS01",
"label": "POS Cash Journal"
}'Result
Keep the returned
id→ your POSjournal_id.
Step 3 | Prepare Ledger Accounts (Revenue, VAT, Payment Types)
Tip: Most companies already have these accounts.
If not, guide the user to create or confirm them once, then store the IDs in your POS mapping.
Revenue (Net Sales)
Revenue accounts typically start with 706 (services) or 707 (goods).
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/ledger_accounts?filter=[{"field":"number","operator":"start_with","value":"706"}]' \
-H "Authorization: Bearer <ACCESS_TOKEN>"Tip: Keep one revenue account per VAT rate (e.g., 7061 = 10%, 7062 = 20%) for easier reporting.
If missing, create one:
curl --request POST \
--url https://app.pennylane.com/api/external/v2/ledger_accounts \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"number": "7062",
"label": "Sales – VAT 20%"VAT (Output VAT)
About VAT rate codes
When posting entries that include VAT, make sure to use the correct VAT ledger account (usually
4457…) and, when required, the correspondingvat_ratecode such asFR_200(20%) orFR_100(10%).For details and examples, see the Understanding VAT Rates on Ledger Accounts page.
VAT accounts often start with 4457…
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/ledger_accounts?filter=[{"field":"number","operator":"start_with","value":"4457"}]' \
-H "Authorization: Bearer <ACCESS_TOKEN>"Note: These accounts usually exist already - avoid duplicates.
Payment Types
Payment method accounts often use 511xxx (payment intermediaries) or 530 (cash).
# Cash (530…)
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/ledger_accounts?filter=[{"field":"number","operator":"start_with","value":"53"}]' \
-H "Authorization: Bearer <ACCESS_TOKEN>"
# Payment providers (511…)
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/ledger_accounts?filter=[{"field":"number","operator":"start_with","value":"511"}]' \
-H "Authorization: Bearer <ACCESS_TOKEN>"If missing, create one:
curl --request POST \
--url https://app.pennylane.com/api/external/v2/ledger_accounts \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"number": "511008",
"label": "Uber Eats Clearing"
}'✅ Result:
You now have your mapping:
- Revenue per VAT rate →
ledger_account_id - VAT collected per VAT rate →
ledger_account_id - Payment methods (cash, card, providers) →
ledger_account_id
Store this mapping in your POS for reuse.
Step 4 | Handle Rounding Differences (required)
Ticket Z totals must be balanced:
Sum(Credits) = Revenue + VAT; Sum(Debits) = Payments.
Small rounding gaps happen. Add a balancing line:
- If Credit < Debit → add Credit line to account 758 (miscellaneous income)
- If Debit < Credit → add Debit line to account 658 (miscellaneous expense)
Fetch the account ID(s) if needed:
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/ledger_accounts?filter=[{"field":"number","operator":"start_with","value":"658"}]' \
-H "Authorization: Bearer <ACCESS_TOKEN>"Step 5 | (Optional) Attach the Ticket Z PDF File
curl --request POST \
--url https://app.pennylane.com/api/external/v2/file_attachments \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: multipart/form-data" \
-F [email protected]✅ Result:
Keep the returned id to associate later with your ledger entry.
Step 6 | Post the Ticket Z as a Ledger Entry
Build one balanced entry per day (or per shift).
curl --request POST \
--url https://app.pennylane.com/api/external/v2/ledger_entries \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"date": "2025-01-01",
"label": "Ticket Z – Main Register – 2025-01-01",
"journal_id": <YOUR_JOURNAL_ID>,
"ledger_entry_lines": [
{ "debit": "0.00", "credit": "90.91", "ledger_account_id": <YOUR_REVENUE_ACCOUNT_ID> },
{ "debit": "0.00", "credit": "9.09", "ledger_account_id": <YOUR_VAT_ACCOUNT_ID> },
{ "debit": "100.00","credit": "0.00", "ledger_account_id": <YOUR_PAYMENT_ACCOUNT_ID> }
]
}'
✅ Result: 201 Created - entry successfully posted.
Tip: Amounts must be strings ("100.00") and the entry must be perfectly balanced.
Step 7 | Validate & Troubleshoot
Common errors
401 Unauthorized→ Check OAuth token or header format.403 Forbidden→ Missing scope (ledger), or wrong company context.422 Entry lines are not balanced→ Recalculate totals or add a rounding line.
Validation checklist (per company)
- POS journal selected and stored (
journal_id) - Revenue, VAT, and payment accounts mapped to IDs
- Ticket Z totals computed per VAT rate and payment type
- Entry lines balanced; rounding handled via 658 / 758
Verifying your entries
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/ledger_entries?filter=[{"field":"date","operator":"eq","value":"2025-01-03"},{"field":"journal_id","operator":"eq","value":<YOUR_JOURNAL_ID>}]' \
-H "Authorization: Bearer <ACCESS_TOKEN>"Note:
GET /ledger_entries/{id}isn’t available in all environments.Use the list endpoint or check directly in the UI.
Allowed filters;
Fields →
updated_at,created_at,date,journal_idOperators →
eq,not_eq,lt,gt,lteq,gteq
Best Practices
- One mapping step per company — confirm journal & accounts once, then reuse.
- One entry per business cycle — daily (Ticket Z) or per shift, be consistent.
- Attach artifacts — add the Ticket Z PDF for auditability.
- Ensure idempotency — reuse a stable label or reference to avoid duplicates.
- Observe limits — keep entries concise and balanced.
✅ Following these practices ensures stable, auditable sales posting and cleaner books.
Updated about 3 hours ago
