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 typePRODUCT_TABLE. The field’s fieldMeta object holds the whole table:
Create a product table
To add a product table to a draft document, send aPOST request to the fields endpoint. Replace DOCUMENT_ID with the document’s ID and API_KEY with your API key:
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 inproducts is one row. Only id and name are required, but a row without a price value contributes nothing to the totals.
Columns
Thecolumns 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
Thepricing 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 atype 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: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:
Let a recipient choose
To turn a fixed price list into an offer the signer responds to, setallowSelection 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.
SIGNER_ID with the ID of the signer who makes the choice.
Recipient choices
TherecipientChoice object records what the signer chose:
defaultSelected decides which optional rows count.
Group rows into sections
A section groups rows under a heading. Each entry insections 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
Setbilling 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’sproductType 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 aPATCH request with the complete fieldMeta object. The request replaces the metadata rather than merging into it, so include every property you want to keep:
DOCUMENT_ID: the document’s IDFIELD_ID: the field’s IDAPI_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

