Skip to main content

Product tables

A product table presents products or services with pricing inside a document. Use one for quotes, order forms, service agreements, and any document where the signer needs to see what they’re paying for. A product table can also ask the signer to choose. Mark rows as optional, group alternatives so that the signer picks one, or let the signer set a quantity. sajn recalculates the totals from what the signer chose and seals those amounts into the signed PDF file.

How a product table is structured

A product table is a document field of type PRODUCT_TABLE. The field’s fieldMeta object holds the whole table:

Create a product table

To add a product table to a draft document, send a POST request to the fields endpoint. Replace DOCUMENT_ID with the document’s ID and API_KEY with your API key:
The position value is the field’s 0-based order in the document, not a coordinate. The optional key value lets you address the field later without storing its ID. To create several fields in one request, send an array of field objects instead of a single object.
You can add and change fields only while the document is in DRAFT status. A request against a sent document returns an HTTP 400 Bad Request status code.

Product rows

Each entry in products is one row. Only id and name are required, but a row without a price value contributes nothing to the totals.

Columns

The columns object decides which columns render and what they’re called. Each key maps to an object with name, originalName, key, enabled, and settings. The key value must be a product property or lineTotal, which is calculated rather than stored. To hide a column without deleting it, set enabled to false. The originalName value keeps the column’s original label so that sajn can retranslate a column you haven’t renamed. The settings object accepts decimalPlaces, pricePrefix, and priceSuffix. The price and moms columns also accept pricesIncludeMoms, defaultMomsRate, and showMomsBreakdown, but set those through pricing instead. sajn reads the column settings only for tables authored before pricing existed.

Pricing and moms

The pricing object controls how sajn reads your prices: Set pricesIncludeMoms before you enter prices. It changes what every price value means, so switching it later changes every total in the table.

Discounts

A discount is an object with a type of PERCENT or AMOUNT and a numeric value. A PERCENT discount takes that percentage off; an AMOUNT discount takes that many currency units off. sajn never discounts below zero. Set discount on a product row to reduce that row, or on the table to reduce the whole table. A table discount also accepts a label string, which replaces the default summary label. An AMOUNT discount is measured in the basis you entered prices in. On a table with pricesIncludeMoms set to true, a 500 kr discount takes 500 kr off the total the signer pays. On a table with prices excluding moms, the same discount takes 500 kr off the subtotal, and moms is charged on what’s left.

How totals are calculated

sajn calculates each included row, then applies the table discount:
When prices exclude moms, net is the row’s amount excluding moms, and moms is net × momsRate / 100. When prices include moms, sajn divides the moms back out: the amount excluding moms is net / (1 + momsRate / 100), and moms is the remainder. The table’s subtotal value is the sum of every row excluding moms. sajn then applies the table discount and scales moms by the same proportion:
For the three rows in Create a product table, with prices excluding moms and a 10% table discount:

Let a recipient choose

To turn a fixed price list into an offer the signer responds to, set allowSelection to true and name the signer in selectionSignerId. Mark the rows you want to offer with optional. The signer fills in their choice before they sign, and the totals follow it. To block signing until the signer has chosen, set selectionRequired to true. A table where nothing is selectable counts as answered, so selectionRequired never traps a signer on a fixed price list. To let the signer set a quantity, set quantityEditable on the row and bound it with quantityMin and quantityMax. sajn clamps whatever the signer enters to that range.
Replace SIGNER_ID with the ID of the signer who makes the choice.

Recipient choices

The recipientChoice object records what the signer chose:
sajn writes this object when the signer fills in the document, and reads it back when it calculates the totals and renders the signed PDF file. You can read it through the API, but you can’t submit a choice on the signer’s behalf. Before the signer answers, defaultSelected decides which optional rows count.

Group rows into sections

A section groups rows under a heading. Each entry in sections needs an id and a name, and each row joins a section through sectionId. A section with pickOne set to true presents its rows as alternatives. Give one row defaultSelected so that the section has an answer before the signer responds:
Pick-one applies whether or not allowSelection is set. A pick-one section with no chosen row and no defaultSelected row contributes nothing to the document, so always mark a default.

Bill on an interval

Set billing on a row to MONTHLY, QUARTERLY, or YEARLY to charge it on that interval. Rows without a billing value are one-time charges. As soon as one row is recurring, the summary adds a total per interval alongside the grand total, so the signer sees what they pay once and what they pay each period.

ROT and RUT deductions

Set a row’s productType to ROT or RUT to mark labor that qualifies for the Swedish tax deduction. sajn deducts 30% of a ROT row and 50% of a RUT row, both measured including moms, and reports the result as the payable amount. The deduction is per row. Mark the labor rows and leave materials as GOODS, because materials don’t qualify.

Update a product table

To change a table, send a PATCH request with the complete fieldMeta object. The request replaces the metadata rather than merging into it, so include every property you want to keep:
Replace the following:
  • DOCUMENT_ID: the document’s ID
  • FIELD_ID: the field’s ID
  • API_KEY: your API key

Troubleshoot

Next steps

Creating documents

Create the document that holds the table

Templates and forms

Reuse a product table across documents

Document fields API

Read the full field reference

Send for signing

Send the document to its signers