Valverna

Transactions · Partners

Create a transaction

Write settled transactions into Valverna on behalf of merchants. Documents are strictly validated against the rule pack selected by the business's country and the documentTier — see the unified schema.

Endpoint

POST /v1/transactions Partners only

Requires the transactions.write scope on a vlv_ access token. All monetary values are integer minor units (cents, öre) — in the request and in the response. Valverna auto-generates id, createdAt, and source.syncedAt.

Request format

Content-Type optional
Must be application/json (a charset parameter is allowed). A missing or other content type returns 415 with code unsupported_media_type.
Unknown fields optional
Send only documented fields. An unrecognized top-level or nested field returns 400 with code invalid_json. The metadata and extensions objects are the designed extension points and still accept arbitrary keys.

Full example

A Swedish café sale in June 2026 — food at the temporary 6% rate, merchandise at 25%, paid cash so totals.rounding is required.

Request body

{
  "type": "sale",
  "settled": true,
  "receiptNumber": "R-2026-004521",
  "timestamp": "2026-06-05T14:32:00+02:00",
  "currency": "SEK",
  "locale": "sv-SE",

  "vendor": { "id": "biz_9f3a2c81", "locationName": "Stureplan" },  // everything else auto-filled

  "items": [
    {
      "name": "Cappuccino",
      "quantity": 2,
      "unitPrice": 4900,
      "amount": 9800,
      "taxRate": 6,
      "taxCategory": "S",
      "taxAmount": 555,
      "category": "food_beverage"
    },
    {
      "name": "Tygkasse med logga",
      "quantity": 1,
      "unitPrice": 14900,
      "amount": 14900,
      "taxRate": 25,
      "taxCategory": "S",
      "taxAmount": 2980,
      "category": "merchandise"
    }
  ],

  "tax": {
    "scheme": "SE_MOMS",
    "pricesIncludeTax": true,
    "summary": [
      { "rate": 6, "category": "S", "label": "Moms 6%", "taxableAmount": 9245, "taxAmount": 555 },
      { "rate": 25, "category": "S", "label": "Moms 25%", "taxableAmount": 11920, "taxAmount": 2980 }
    ]
  },

  "totals": {
    "subtotal": 24700,
    "discount": 0,
    "tax": 3535,
    "rounding": 0,
    "total": 24700
  },

  "payment": { "method": "cash" },

  "extensions": {
    "se": {
      "controlUnitId": "CU-SE-20241234",
      "controlCode": "ABCD 1234 EFGH 5678",
      "sequenceNumber": 4521
    }
  },

  "source": {
    "platform": "acme_pay",
    "externalId": "pay_9f2a8b1c"
  },

  "metadata": { "invoiceId": "INV-2026-031" }
}

profileId and documentTier are omitted here — they default to the business country's receipt profile (a Swedish business gets se-receipt-1 / receipt).

Root fields

type required
Transaction type. One of sale, refund, void, credit_note, exchange.
settled required
Has money changed hands? true = final, false = incomplete/failed/void.
receiptNumber required
As issued by your POS/platform. Sequential, human-readable. Uniqueness per vendor location is your responsibility — the gapless-series duty sits on the POS/control unit. Valverna never deduplicates by this field.
timestamp required
When the transaction occurred. ISO 8601 with timezone.
currency required
ISO 4217. SEK, NOK, EUR, USD, etc. Must be SEK under se-receipt-1.
profileId optional
Which rule pack the document satisfies. Best omitted — Valverna always resolves it from the business country (e.g. se-receipt-1). If you do send it, it must equal the resolved profile or the request fails 422 with code profile_mismatch.
documentTier optional
Defaults to receipt. Invoice tiers (simplified_invoice, full_invoice) are reserved and not accepted yet.
schemaVersion optional
Server-managed — don't send it. Valverna stamps the canonical schema version (currently "2.1") on the stored and returned document; any value you send is overwritten.
locale optional
BCP 47 for rendering. sv-SE, en-US. Used for receipt display.
metadata optional
Arbitrary key-value pairs. Preserved as-is, never used in validation.

Vendor

The stored document carries a full vendor snapshot, but you only send the reference. Everything else — name, legalName, orgNumber, vatNumber, country, address — is auto-filled from the business record at write time.

vendor.id required
The Valverna business id this transaction belongs to. Must match a business that granted your app access.
vendor.locationName optional
The only other vendor field you may send. Store/branch name — required for SE multi-location businesses.

Line items

The heart of the receipt. At least one item required. All monetary values in minor units.

name required
Item name as it appears on the receipt.
quantity required
Supports decimals for weighted goods (1.5 kg). Negative for returned items in exchanges.
unit optional
pcs, kg, l, m, hour, etc. Defaults to pcs.
unitPrice required
Price per unit. Minor units. Inclusive or exclusive of tax per tax.pricesIncludeTax.
amount required
quantity × unitPrice after discount. Minor units. Negative for returned items.
taxRate required
Tax rate as percentage, as applied at transaction time. Validated against the scheme's rate table — SE_MOMS accepts 25, 12, 6, and 0. Date-windowed enforcement (e.g. SE food at 12% before 2026-04-01, 6% after) is planned.
taxCategory required
S (standard/reduced), Z (zero-rated), E (exempt), AE (reverse charge), O (out of scope). See tax categories.
taxAmount required
Tax for this line. Minor units. Negative for returned items.
discount optional
Object with amount (positive, minor units, already deducted from line amount) and optional description.
category optional
Product category. Useful for rate-table validation and analytics.
sku optional
Product code / SKU / EAN.

Tax

tax.scheme required
Selects the rate table rates are validated against. SE_MOMS, NO_MVA, DK_MOMS, FI_ALV, EU_VAT, UK_VAT, US_SALES_TAX. Must be SE_MOMS under se-receipt-1. When a Nordic vendor (SE/NO/DK/FI) uses EU_VAT (the EU OSS scheme), the sale must be a genuine cross-border distance sale: customer.country is required, valid, and different from the vendor's country — otherwise the write fails 422 with code customer.country/required, customer.country/invalid_country, or tax.scheme/scheme_not_allowed (customer in the vendor's own country). The OSS pack also requires channel == ecommerce.
tax.pricesIncludeTax required
true in EU/Nordic (VAT-inclusive). false in US (tax added at checkout).
tax.reverseCharge optional
true for B2B reverse charge (EU cross-border). Required when any line uses category AE.
tax.summary required
Array. One entry per (rate, category) pair used in items. Each entry: rate, category, optional label (e.g. "Moms 25%"), taxableAmount (minor units), taxAmount (minor units).
tax.jurisdictions optional
US only. Multi-level tax breakdown. Each entry: level (state, county, city, district), name, rate, taxAmount.

Totals

All values in minor currency units.

totals.subtotal required
Sum of line item amount values.
totals.discount required
Total discount across all items. 0 if none.
totals.tax required
Total tax. Must equal the sum of tax.summary[].taxAmount.
totals.rounding optional
Cash rounding adjustment, signed. Required when payment.method == "cash" — send 0 when the total already lands on the increment. SEK rounds the total to the nearest whole krona, never touching the VAT base. Sweden’s öresavrundning is statutory, so it may also appear on card/other tenders (the rounded total must still land on the krona grid); for NO/DK/FI electronic tenders settle exact, so it must be absent or 0.
totals.total required
Final amount after rounding. Non-negative for sales: 0 represents a completed free or fully discounted conversion. Refunds and credits remain positive; voids are 0.

Payment

Minimal tender information — the legal floor, not processor data. Required by the SE receipt pack (betalningsmedel is a mandatory receipt field).

payment.method required
cash, card, mobile, transfer, other. mobile covers Swish, Vipps, MobilePay.
payment.last4 optional
Last 4 digits of the card PAN, for receipt matching. Never more digits, never an expiry date.

Source

Provenance — where this transaction came from. Not payment details.

source.platform required
Your platform slug for the originating system — e.g. acme_pay, sumup, izettle. There are no reserved values: any string is accepted, including stripe, shopify, or sumup. Dedup is namespaced per writing app, so your platform never collides with Valverna's own first-party adapters or with other partners.
source.externalId required
The transaction ID in the source platform. Together with platform (and the business and your app), this is the identity used for deduplication.

Customer

Optional customer-level data — accepted and persisted as-is with the document. Useful for consumer-facing receipt delivery and B2B compliance fields. Every field is optional.

customer.name optional
Customer name as it appears on the receipt.
customer.email optional
For digital receipt delivery.
customer.orgNumber optional
B2B — the buyer's organization number.
customer.vatNumber optional
B2B — the buyer's VAT number, e.g. for reverse-charge documentation.
customer.country optional
ISO 3166-1 alpha-2 (e.g. DE). Optional in general, but required when a Nordic vendor (SE/NO/DK/FI) uses the EU OSS scheme — see Tax.

Linked transactions

Required for refund, credit_note, exchange, and void types. Links back to the original sale.

linkedTransaction.originalTransactionId required
The id of the original sale being refunded/voided/credited.
linkedTransaction.reason optional
returned, defective, overcharge, price_adjustment, duplicate, other.
linkedTransaction.note optional
Free-text explanation.

Extensions

Country-specific fields required by tax authorities, namespaced under extensions (this replaces the v2.0 fiscal object). The se namespace is required under se-receipt-1.

extensions.se.controlUnitId required
Certified kontrollenhet serial number — or the control-system code for a cloud kontrollsystem. Required by Skatteverket.
extensions.se.controlCode required
Kontrollkod generated by the control unit/system for this receipt.
extensions.se.sequenceNumber required
Sequential receipt counter from the control unit / POS register.

Validation errors

Documents are validated against the selected rule pack — arithmetic invariants, required fields, and rates against the scheme's rate table (date-windowed rate enforcement is planned). Failures return 422 with a list of {field, code, message} issues, so you can surface every problem in one round trip.

Response 422 Unprocessable Entity

{
  "error": {
    "code": "validation_failed",
    "message": "2 validation issue(s)"
  },
  "issues": [
    {
      "field": "items[0].taxRate",
      "code": "invalid_rate",
      "message": "rate 19.000% is not in the SE_MOMS table effective at 2026-06-05"
    },
    {
      "field": "totals.rounding",
      "code": "required",
      "message": "rounding is required when payment.method is cash (0 when the total already lands on the increment)"
    }
  ]
}

Deduplication & idempotency

The identity of an ingested transaction is (business, source.platform, source.externalId) as written by your app — dedup is namespaced per writing app, so your writes never collide with Valverna's own first-party adapters or with other partners. Never receiptNumber.

201 Created

First write for this (platform, externalId) pair. The document is stored.

200 OK

Replay with the same Idempotency-Key — the existing document is returned unchanged. Safe to retry on network failure.

409 Conflict

Same (platform, externalId) posted again with a different (or missing) Idempotency-Key. The transaction already exists — error code duplicate_transaction.

Request header

Idempotency-Key: acme_txn_2026-06-05_a8f2b1c9

The Idempotency-Key is optional and is the tiebreaker that makes a re-POST of the same source identity a 200 idempotent replay instead of a 409 duplicate. It must be printable ASCII and at most 255 characters; otherwise the request fails 400 with code invalid_idempotency_key.

Response

Status 201 Created

Returns the full canonical document — your request plus the Valverna-generated id, createdAt, source.syncedAt, the server-stamped schemaVersion (currently "2.1"), the resolved profileId and documentTier, and the embedded vendor snapshot.