Menu
Cookbooks
API reference
- Overview
- Companies
- Capabilities
- Measures
- Packages
- Unexplained
- Liquidity forecast
- Portfolio
- Customers
- Invoices
- Collections
- Articles
- Set-offs
- Suppliers
- Supplier invoices
- Supplier payment files
- Bank payments
- Expense claims
- Transactions
- Reconciliation
- Bank accounts
- POS sales
- Webshop orders
- Journal entries
- Voucher gap explanations
- Fiscal periods
- Accounts
- Accrual schedules
- Agreements and commitments
- Tax loss carry-forwards
- Fixed assets
- Documents
- Signing requests
- Company registers
- Capital events
- Owner tax
- Inbox items
- Dimensions
- Employees
- Salary runs
- Reports
- Budgets
- Imports
- Compliance check
- Skatteverket
- Peppol
- Webhooks
- Operations
- Pending operations
- Health
- API key
- Service accounts
Webshop orders
The order and refund rows the Shopify, WooCommerce and Zettle syncs deliver (VAT per rate, country, booking status), one order with the booking the Orders page would prefill, and booking one order with your own lines, linked to the order with its orderunderlag.
Endpoints
GET/api/v1/companies/:companyId/webshop-orders: The order and refund rows synced from the company's webshops, newest first.GET/api/v1/companies/:companyId/webshop-orders/:orderId: One webshop order row with its line items and the booking the Orders page would prefill.GET/api/v1/companies/:companyId/webshop-orders/oss-underlag: Underlag for the OSS declaration (unionsordningen): a quarter's webshop sales to consumers in other EU countries, per country and VAT rate, in euro.POST/api/v1/companies/:companyId/webshop-orders/:orderId/book: Book one webshop order or refund with your own lines, linked to the order with its orderunderlag.POST/api/v1/companies/:companyId/webshop-orders/book-day: Book a store's orders and refunds of one day and one payment method as one verifikat, with a day summary as underlag.
GET /api/v1/companies/:companyId/webshop-orders
webshop-orders.list · scope transactions:read
The order and refund rows synced from the company's webshops, newest first.
Lists the rows the Shopify, WooCommerce and Zettle syncs store: one per paid order and one per refund (a negative row under its order), with amounts, the VAT split per rate, the country, the payment method and where each row stands (booking_status: to_book, booked, marked_booked, invoiced, in_transactions, unpaid). Filter by booked or unbooked, platform, row type, order date and country. Paginated with limit and offset; total is the full count. Read-only.
Use when: To find the orders to book, or to see which orders a period holds per country.
Don't use for: One order with its line items and suggested booking (GET /webshop-orders/{orderId}).
Pitfalls
- customer_country is ISO 3166-1 alpha-2. Shopify stores the shipping (destination) country, or the billing country when nothing on the order ships (digital products, services); WooCommerce the billing country. It is null for a Shopify local pickup or when the store withholds addresses.
- booked=unbooked also returns unpaid and invoiced rows: only booking_status to_book can be booked.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
booked | "booked" | "unbooked" | no | booked: booked here or marked as booked outside; unbooked: the rest. |
platform | "woocommerce" | "shopify" | "zettle" | no | |
row_type | "order" | "refund" | no | |
from | string | no | First order date, inclusive. |
to | string | no | Last order date, inclusive. |
country | string | no | ISO 3166-1 alpha-2 country, e.g. DK. |
limit | number | no | Rows per page, default 50. |
offset | number | no |
Response fields
| Name | Type |
|---|---|
orders | object[] |
total | number |
Example request
{
"booked": "unbooked",
"country": "DK"
}
Example response
{
"data": {
"orders": [
{
"webshop_order_id": "3f2a…",
"platform": "shopify",
"store_scope": "exempel.myshopify.com",
"store_label": "Exempelbutiken",
"row_type": "order",
"parent_webshop_order_id": null,
"order_number": "#1042",
"platform_status": "paid",
"is_paid": true,
"order_date": "2026-10-14",
"paid_date": "2026-10-14",
"currency": "SEK",
"total": 1250,
"total_tax": 250,
"total_sek": 1250,
"exchange_rate": 1,
"vat_breakdown": [
{
"rate": 25,
"net": 1000,
"tax": 250
}
],
"customer_country": "DK",
"customer_name": null,
"customer_company": null,
"payment_method": "shopify_payments",
"payment_method_title": "shopify_payments",
"refunded_total": 0,
"booking_status": "to_book",
"journal_entry_id": null,
"invoice_id": null,
"manually_booked_journal_entry_id": null,
"remote_changed_after_freeze": false
}
],
"total": 1
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
GET /api/v1/companies/:companyId/webshop-orders/:orderId
webshop-orders.get · scope transactions:read
One webshop order row with its line items and the booking the Orders page would prefill.
Everything GET /webshop-orders returns for the row plus its line items and suggestion: the entry date (paid date, else order date), the open fiscal period covering it, the description, the payment account from the store's payment-method mapping (1686 when unmapped), and balanced lines with revenue and VAT per rate: on the store's accounts for the order's destination country when the store has set them, otherwise on the Swedish accounts. warnings lists what the suggestion cannot know, e.g. a country outside Sweden that no store rule covers. Read-only.
Use when: Before booking an order, to start from the dialog's lines and change the accounts that the country or VAT rate calls for.
Don't use for: Listing orders (GET /webshop-orders) or booking (POST /webshop-orders/{orderId}/book).
Pitfalls
- customer_country is ISO 3166-1 alpha-2. Shopify stores the shipping (destination) country, or the billing country when nothing on the order ships (digital products, services); WooCommerce the billing country. It is null for a Shopify local pickup or when the store withholds addresses.
- For a Shopify order shipped outside Sweden the suggested lines use the store's accounts for that country and VAT rate when the store has set them (Settings > Connections > Shopify), e.g. 3106/2670 for OSS; an order shipped to Sweden uses the store's own sales account for Sweden when it has set one. Otherwise they use Swedish accounts (3001-3004, 2611-2631): for an OSS, export or VOEC sale choose the accounts yourself. The account choice is the caller's.
- suggestion.lines is null while a non-SEK order has no exchange rate; booking resolves the rate once more and refuses with WEBSHOP_ORDER_FX_UNRESOLVED if it still cannot.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Response fields
| Name | Type |
|---|---|
webshop_order_id | string |
platform | "woocommerce" | "shopify" | "zettle" |
store_scope | string |
store_label | string | null |
row_type | "order" | "refund" |
parent_webshop_order_id | string | null |
order_number | string |
platform_status | string |
is_paid | boolean |
order_date | string |
paid_date | string | null |
currency | string |
total | number |
total_tax | number |
total_sek | number | null |
exchange_rate | number | null |
vat_breakdown | object[] |
customer_country | string | null |
customer_name | string | null |
customer_company | string | null |
payment_method | string | null |
payment_method_title | string | null |
refunded_total | number |
booking_status | "booked" | "marked_booked" | "invoiced" | "in_transactions" | "unpaid" | "to_book" |
journal_entry_id | string | null |
invoice_id | string | null |
manually_booked_journal_entry_id | string | null |
remote_changed_after_freeze | boolean |
line_items | object[] |
suggestion | object |
warnings | object[] |
Example response
{
"data": {
"webshop_order_id": "3f2a…",
"platform": "shopify",
"store_scope": "exempel.myshopify.com",
"store_label": "Exempelbutiken",
"row_type": "order",
"parent_webshop_order_id": null,
"order_number": "#1042",
"platform_status": "paid",
"is_paid": true,
"order_date": "2026-10-14",
"paid_date": "2026-10-14",
"currency": "SEK",
"total": 1250,
"total_tax": 250,
"total_sek": 1250,
"exchange_rate": 1,
"vat_breakdown": [
{
"rate": 25,
"net": 1000,
"tax": 250
}
],
"customer_country": "DK",
"customer_name": null,
"customer_company": null,
"payment_method": "shopify_payments",
"payment_method_title": "shopify_payments",
"refunded_total": 0,
"booking_status": "to_book",
"journal_entry_id": null,
"invoice_id": null,
"manually_booked_journal_entry_id": null,
"remote_changed_after_freeze": false,
"line_items": [
{
"name": "Skidglasögon",
"quantity": 1,
"total": 1000,
"total_tax": 250,
"vat_rate": 25
}
],
"suggestion": {
"entry_date": "2026-10-14",
"fiscal_period_id": "a8f1…",
"description": "Order #1042 (shopify_payments)",
"payment_account": "1686",
"payment_account_mapped": false,
"lines": [
{
"account_number": "1686",
"debit_amount": 1250,
"credit_amount": 0,
"line_description": "Order #1042 (shopify_payments)"
},
{
"account_number": "3001",
"debit_amount": 0,
"credit_amount": 1000,
"line_description": "Order #1042 (shopify_payments)"
},
{
"account_number": "2611",
"debit_amount": 0,
"credit_amount": 250,
"line_description": "Order #1042 (shopify_payments)"
}
]
},
"warnings": [
{
"code": "country_outside_sweden",
"message_sv": "Ordern har ett land utanför Sverige…",
"message_en": "The order has a country outside Sweden…"
}
]
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
GET /api/v1/companies/:companyId/webshop-orders/oss-underlag
webshop-orders.oss-underlag · scope reports:read
Underlag for the OSS declaration (unionsordningen): a quarter's webshop sales to consumers in other EU countries, per country and VAT rate, in euro.
Adds up the webshop orders booked on the OSS accounts (VAT code oss, the OSS output VAT account, or accounts a Shopify country rule names for OSS) in a calendar quarter: per destination country and VAT rate the sales without VAT and the VAT, in euro at the ECB rate for the last day of the quarter (the next publication day when the ECB published none) and in kronor as booked. Refunds of an earlier quarter's sales are corrections of that quarter (country, quarter, VAT). unaccounted lists the OSS-account verifikat that book no order (manual vouchers, stornos), and warnings the orders missing from the underlag. Read-only; nothing is filed.
Use when: Before filing the quarterly OSS declaration in Skatteverket's e-tjänst, or to check what the OSS accounts hold per country.
Don't use for: The Swedish momsdeklaration (OSS sales are outside it), EU sales taxed in Sweden below the distance-selling threshold, or invoices (they carry Swedish VAT only).
Pitfalls
- Only booked orders count, in the quarter of the verifikat that books them: book the quarter's orders first (warnings unbooked_orders).
- Euro amounts are null until the ECB has published the rate for the last day of the quarter (warnings rate_unavailable); the kronor amounts are final.
- The VAT is declared with two decimals, never rounded. A nil_return quarter still needs a declaration.
- A correction uses the ECB rate of the quarter it corrects, so a full refund takes back exactly what was declared.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
year | number | yes | Calendar year; the OSS scheme started 1 July 2021. |
quarter | number | yes | Calendar quarter, 1-4. |
Response fields
| Name | Type |
|---|---|
period | object |
rows | object[] |
corrections | object[] |
totals | object |
rates | object[] |
ledger_vat_sek | number |
unaccounted | object[] |
warnings | object[] |
nil_return | boolean |
Example request
{
"year": 2026,
"quarter": 3
}
Example response
{
"data": {
"period": {
"year": 2026,
"quarter": 3,
"start": "2026-07-01",
"end": "2026-09-30",
"label": "Q3 2026",
"deadline": "2026-10-31"
},
"rows": [
{
"country": "DE",
"vat_rate": 19,
"net_eur": 88.26,
"vat_eur": 16.77,
"net_sek": 1000,
"vat_sek": 190,
"order_count": 1
}
],
"corrections": [],
"totals": {
"net_eur": 88.26,
"vat_eur": 16.77,
"corrections_vat_eur": 0,
"net_sek": 1000,
"vat_sek": 190,
"corrections_vat_sek": 0
},
"rates": [
{
"currency": "SEK",
"for_date": "2026-09-30",
"rate": 11.331,
"rate_date": "2026-09-30",
"unavailable": false
}
],
"ledger_vat_sek": 190,
"unaccounted": [],
"warnings": [],
"nil_return": false
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/webshop-orders/:orderId/book
webshop-orders.book · scope bookkeeping:write
Book one webshop order or refund with your own lines, linked to the order with its orderunderlag.
Posts one verifikat for the order row through the bookkeeping engine (source_type webshop_order, linked to the order) with the lines you send, exactly as the Orders page books it: the order stops showing as unbooked, and the orderunderlag (line items, VAT, country, payment method) is archived on the verifikat. entry_date defaults to the paid date (else the order date), fiscal_period_id to the open period covering it, description to the Orders page's. Refused when the order is booked, invoiced, marked as booked, unpaid or already in the transactions feed. Dry-runnable.
Use when: An order has booking_status to_book and you know its accounts, e.g. OSS or VOEC accounts picked from its country and VAT rate.
Don't use for: Orders that should become a customer invoice, booking a payout from the payment provider (book that against the clearing account from the bank row), or correcting a booked order (storno).
Pitfalls
- Send the complete line set; sum debit = sum credit > 0. Start from suggestion.lines of GET /webshop-orders/{orderId} and change the accounts.
- The account choice is yours: Accounted only checks balance, period, lock and that the accounts exist. Shopify's amounts and VAT are the source; the lines should add up to them.
- customer_country is ISO 3166-1 alpha-2. Shopify stores the shipping (destination) country, or the billing country when nothing on the order ships (digital products, services); WooCommerce the billing country. It is null for a Shopify local pickup or when the store withholds addresses.
- A posted verifikat is permanent: undo it with storno (POST /journal-entries/{id}/reverse), never by editing.
Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes · Stage for approval: yes (?stage=true)
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
dry_run | string | no | true (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits. |
stage | string | no | true stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
lines | object[] | yes | The verifikat lines, balanced. Start from suggestion.lines of gnubok_get_webshop_order. |
entry_date | string | no | Defaults to the paid date, else the order date. |
fiscal_period_id | string | no | Defaults to the open fiscal period covering entry_date. |
description | string | no | Defaults to "Order <number> (<payment method>)". |
voucher_series | string | no | |
notes | string | no |
Response fields
| Name | Type |
|---|---|
webshop_order_id | string |
journal_entry_id | string |
voucher_series | string | null |
voucher_number | number | null |
entry_date | string |
underlag_archived | boolean |
Example request
{
"lines": [
{
"account_number": "1686",
"debit_amount": 1250,
"credit_amount": 0
},
{
"account_number": "3001",
"debit_amount": 0,
"credit_amount": 1000
},
{
"account_number": "2611",
"debit_amount": 0,
"credit_amount": 250
}
]
}
Example response
{
"data": {
"webshop_order_id": "3f2a…",
"journal_entry_id": "9a0b…",
"voucher_series": "A",
"voucher_number": 214,
"entry_date": "2026-10-14",
"underlag_archived": true
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/webshop-orders/book-day
webshop-orders.book-day · scope bookkeeping:write
Book a store's orders and refunds of one day and one payment method as one verifikat, with a day summary as underlag.
Posts one verifikat (source_type webshop_order, dated the day) for every unbooked, paid order and refund of the store whose paid date (else order date) is the day and whose payment method is the one given. The lines are the server's, never the caller's: each order's lines are derived exactly as the Orders page's bulk booking derives them (the store's payment-method mapping and country rules), then summed per account and side, so sales and refunds are separate lines. Every order is linked to the verifikat and stops showing as unbooked, and a dagssammanställning listing each order (number, date, country, amount, VAT) is archived on it. An order that cannot be booked without review (no VAT split, a country no store rule covers, an unknown destination, a non-Swedish rate on the Swedish accounts, an invoice-flow payment method, a rounding gap over 1 kr) is left out, listed in skipped with the reason, and stays unbooked. Dry-runnable.
Use when: The company books its webshop sales per day and payment method (e.g. one verifikat for Shopify Payments and one for PayPal), not per order.
Don't use for: Payouts and fees from the payment provider (book those against the clearing account from the bank row), one order (POST /webshop-orders/{orderId}/book), or correcting a booked day (storno, then book the orders again).
Pitfalls
- Pass order_ids and lines_sha256 from the dry run as expected_order_ids and expected_lines_sha256: an order booked or changed since answers 409 WEBSHOP_DAY_CHANGED instead of booking figures you did not review.
- payment_method is the order's payment_method from GET /webshop-orders, e.g. shopify_payments or paypal. Orders without one cannot be booked per day.
- Orders that sync after the day is booked stay unbooked: booking the day again posts a second verifikat for them.
- skipped orders stay in Att bokföra: book them one by one with POST /webshop-orders/{orderId}/book.
- A posted verifikat is permanent: undo it with storno (POST /journal-entries/{id}/reverse), never by editing.
Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes · Stage for approval: yes (?stage=true)
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
dry_run | string | no | true (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits. |
stage | string | no | true stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
platform | "woocommerce" | "shopify" | "zettle" | yes | |
store_scope | string | yes | The store, store_scope from GET /webshop-orders, e.g. exempel.myshopify.com. |
date | string | yes | The day: the orders' paid date, else their order date. |
payment_method | string | yes | The orders' payment_method from GET /webshop-orders, e.g. shopify_payments or paypal. |
expected_order_ids | string[] | no | The order rows you reviewed (order_ids from the dry run). Refused if any of them was booked or no longer books. |
expected_lines_sha256 | string | no | lines_sha256 from the dry run. Refused if the verifikat lines differ, e.g. an order changed in the store since. |
Response fields
| Name | Type |
|---|---|
journal_entry_id | string |
voucher_series | string | null |
voucher_number | number | null |
entry_date | string |
booked | object[] |
skipped | object[] |
underlag_document_id | string | null |
Example request
{
"platform": "shopify",
"store_scope": "exempel.myshopify.com",
"date": "2026-10-14",
"payment_method": "shopify_payments"
}
Example response
{
"data": {
"journal_entry_id": "9a0b…",
"voucher_series": "L",
"voucher_number": 41,
"entry_date": "2026-10-14",
"booked": [
{
"webshop_order_id": "3f2a…",
"order_number": "#1042",
"row_type": "order",
"total_sek": 1250
},
{
"webshop_order_id": "7c1d…",
"order_number": "#1043",
"row_type": "order",
"total_sek": 499
}
],
"skipped": [],
"underlag_document_id": "5d2c…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}