KintsugiKintsugi

2. Product & Customer Records

Product and customer records are the foundation of every Kintsugi integration. Products carry the tax classification that decides whether an item is taxable in a given jurisdiction. Customer records anchor exemptions to the buyers who hold them. This guide covers when to create each record, how to reference them, and how to verify they landed.

Understanding Product Records

A product record maps one of your catalog items to a Kintsugi tax classification. Each record holds:

  • Your identifier for the item (externalId) plus its name and description
  • A tax classification (productCategory and productSubcategory)
  • A tax-exempt flag (taxExempt)
  • An approval status (status)

Transaction lines and tax estimates reference products by externalProductId. Kintsugi reads the matching product's classification to decide whether the item is taxable where your customer is.

Create products first: Transaction lines reference products by externalProductId, so create your catalog before calling POST /transactions. A tax estimate line can instead name a productCategory and productSubcategory pair, which is priced without creating a product, but a pre-built catalog keeps classification consistent across every call.

Creating Product Records

Create products with POST /products.

POST /products
-H "Api-Key: ***"
-H "Api-Version: 2026-07-21"
{
"externalId": "sku-1001",
"name": "Blue T-Shirt",
"productCategory": "Physical",
"productSubcategory": "General Clothing",
"taxExempt": false,
"source": "API"
}

A new product answers 201. Creating is idempotent on externalId and source: sending the same pair again returns the existing product unchanged with 200 instead of creating a duplicate. Use PATCH /products/{product_id} to change it.

Required Fields

  • externalId: Your stable identifier for the product (for example, "sku-1001")
  • name: Product name
  • productCategory: Top-level tax category: Digital, Misc, Physical, or Services
  • productSubcategory: Subcategory label within that category, such as General Clothing or B2B SaaS. An unrecognized category and subcategory pair returns 400
  • taxExempt: Whether tax calculation treats the product as exempt

Optional Fields

  • description: Product description
  • status: Approval status (APPROVED, PARTIALLY_APPROVED, or PENDING)
  • source: Where the record originated, for example API. Defaults to OTHER
  • sourceTaxExempt: The raw tax-exempt signal from your source system, stored for auditing. taxExempt is the flag tax calculation applies

Pull the category list from the API: Supported categories and subcategories are returned by List the product category catalog. Its category and each subcategory label are the exact values productCategory and productSubcategory accept. Read from that endpoint rather than hardcoding values, so your mapping stays valid as the taxonomy grows.

Verifying Product Records

Confirm your catalog with GET /products. The endpoint is cursor-paginated (limit up to 100, default 50, and the cursor from a prior response's nextCursor or previousCursor) and supports:

  • search over product id, externalId, name, and description. The id and externalId must match exactly; name and description match a case-insensitive substring
  • productCategory and productSubcategory to check classification coverage
  • status (comma-separated) to surface anything still PENDING
  • source (comma-separated) to scope results, and orderBy with order to sort them

To fetch one product directly, use GET /products/{product_id} with the Kintsugi product ID returned at creation.

Look up by your own ID: search matches externalId exactly, so GET /products?search=sku-1001 finds the product you created as sku-1001. Saving the id from your create response still gives you the most direct lookup.

Product Creation Workflow

Products carry the tax category that drives every rate lookup. Create them before the first transaction.

Prepare and validate the payload
Five fields are required before you send anything.
externalIdnameproductCategoryproductSubcategorytaxExempt
Pull the category and subcategory values from GET /products/categories rather than hardcoding them.
Create the product
POST/productsReturns the product id
Non-2xx? See error handling below.
Verify it exists
GET/productsOptional, recommended
Product is ready for tax calculations
Reference it by its externalId, as externalProductId, in estimates and transactions.

Understanding Customer Records

A customer record identifies a buyer and gives exemptions something to attach to. Create customer records when you sell to:

  • Nonprofit organizations
  • Government agencies
  • Resellers holding valid exemption certificates
  • Any other exempt entity

Exemptions themselves are separate records created against a customer through Create an exemption. When a tax estimate's customer.externalId matches a customer Kintsugi holds, that customer's exemptions and tax registrations are applied to the estimate.

When you don't need customer records: Selling only to ordinary consumers? Skip customer creation and pass the buyer's details inline on the transaction's customer object.

Creating Customer Records

Create customers with POST /customers. Every field is optional, so send everything you have; in practice, exemption matching and address-based tax work depend on the fields below.

POST /customers
-H "Api-Key: ***"
-H "Api-Version: 2026-07-21"
{
"externalId": "cust-1001",
"name": "Acme Corp",
"email": "jane.doe@example.com",
"source": "API",
"street1": "123 Main St",
"street2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US"
}

A new customer answers 201 and is always ACTIVE. Creating is idempotent on externalId and source, and on connectionId when you send one: sending the same values again returns the existing customer unchanged with 200.

Fields to Send

  • externalId: Your stable identifier for the customer (for example, "cust-1001")
  • name: Customer name
  • email: Contact email address
  • Address fields: street1, street2, city, county, state, postalCode, and country (ISO 3166-1 alpha-2)

Other Optional Fields

  • companyName: Registered or legal business name, when it differs from name
  • phone: Contact phone number
  • source: Where the record originated. Defaults to OTHER
  • connectionId: The connection to attribute the customer to
  • externalFriendlyId: A human-facing identifier from your source system
  • taxRegistrations: The customer's tax registrations, each with a countryCode, taxType, and taxId

Customer Addresses

A customer record carries one address, written as flat fields on the record itself rather than as a list. That address identifies the customer; it does not decide the tax jurisdiction on its own.

Jurisdiction comes from the addresses on the transaction or estimate, where each entry has a type of SHIP_TO, BILL_TO, SHIP_FROM, or BILL_FROM. Tax is sourced to the SHIP_TO address when one is supplied, and to BILL_TO otherwise.

Adding Exemptions

With the customer created, attach the exemption using POST /exemptions. That request needs:

  • exemptionType: What the exemption applies to: customer, wholesale, transaction, or reverse_charge
  • startDate: First day the exemption is in force (YYYY-MM-DD)
  • customerId: The Kintsugi customer ID from your create response

Add endDate, countryCode, jurisdiction, reseller, fein, salesTaxId, and transactionId where they apply. status defaults to ACTIVE, the only status tax calculation applies. Upload the certificate itself, a PDF of at most 10 MB, with POST /exemptions/{exemption_id}/certificates.

Verifying Customer Records

Confirm your customers with GET /customers. The endpoint is cursor-paginated (limit up to 100, default 50, plus cursor) and supports:

  • search over customer id, name, email, externalId, and externalFriendlyId. The id, externalId, and externalFriendlyId must match exactly; name and email match a case-insensitive substring
  • country and state (comma-separated) to scope results by geography
  • source and connectionId (comma-separated) to filter, and sort with order to sort

For an exact lookup by your own identifier, use GET /customers?search=<externalId>. With the Kintsugi customer ID, use GET /customers/{customer_id}.

Customer Creation Workflow

A customer record is only required when exemptions are involved. Everyone else can be passed inline.

QDoes this customer hold an exemption?
NOSkip the customer record
Pass customer data inline on the transaction
The address on the transaction is enough to source the sale.
YESCreate the record so exemptions can attach to it
Prepare and validate the payload
Every field is optional, so send everything you have. Exemption matching and address-based tax depend on these.
externalIdnameemail
Plus the address: street1, city, state, postalCode and country. Run it through address validation first, since a bad address means a wrong rate.
Create the customer
POST/customersReturns the customer id
Non-2xx? See error handling below.
Attach the exemption
Send the customer id as customerId.
POST/exemptionsNeeds the customer id
Then upload the certificate itself to POST /exemptions/{exemption_id}/certificates.
Verify it exists
GET/customersOptional, recommended
Customer is ready
Reference the customer by externalId on tax estimates and transactions.

Using Products and Customers in Transactions

With records in place, reference them from your tax estimates and transactions.

Referencing Products

Each transaction line points at a product through externalProductId:

{
  "items": [
    {
      "externalId": "item-1",
      "externalProductId": "SKU-ABC",
      "date": "2026-01-15T14:30:00Z",
      "quantity": "2",
      "amount": "100.00"
    }
  ]
}

On a tax estimate the lines live in transactionItems and take the same externalProductId. Each line on a transaction response reports, as productId, the Kintsugi product it resolved to when one was matched.

Referencing Customers

Transactions and estimates carry the buyer on a customer object. Send your externalId there:

{
  "customer": {
    "externalId": "cust-1001",
    "name": "Acme Corp",
    "email": "jane.doe@example.com"
  }
}

On a tax estimate, when the externalId matches a customer on file, Kintsugi applies that customer's exemptions and tax registrations. On a transaction, omit customer entirely for a sale with no customer identity, such as a marketplace or point-of-sale sale: the transaction is attributed to your organization's shared unattributed-sales customer.

Updating Product and Customer Records

Update with PATCH /products/{product_id} and PATCH /customers/{customer_id}. Both are partial updates: only the fields you send change. Typical cases:

  • Products: Reclassifying category or subcategory, changing the taxExempt flag, revising name or description
  • Customers: Correcting an address, updating contact details, adding tax registrations

Recategorizing sets the exemption for you: On a product update, taxExempt is honored only when the category is unchanged. When you recategorize, the exemption is derived from the new category and the taxExempt you send is ignored. source is not editable, and reusing another product's externalId returns 409.

On a customer, sending any address field resets addressStatus to UNVERIFIED, and the new address is validated the next time the customer is processed. taxRegistrations upserts each entry on its countryCode and taxType pair and cannot remove one. To retire a customer, DELETE /customers/{customer_id} archives it; creating a customer again with the same externalId and source restores it with its transactions and exemptions attached.

When to update versus create new: If an item's tax treatment fundamentally changes, for example moving from physical goods to a digital download, create a new product under a new externalId instead of editing the old one. That keeps a clean audit trail of when the classification changed.

Best Practices

Product Management

  • Build the catalog first: Have products in place before you wire up tax calculation or transaction sync
  • Read categories from the API: Source values from List the product category catalog instead of hardcoding them
  • Use consistent external IDs: Pick one convention, such as always the SKU, and hold it across every system
  • Batch creation: Send products in chunks rather than all at once
  • Verify before you rely on them: Confirm products exist before referencing them in transactions

Customer Management

  • Create records where exemptions live: Ordinary consumers can travel inline on the transaction
  • Validate addresses: Run addresses through address validation before you save them
  • Store Kintsugi customer IDs: You need the id to attach exemptions and for direct lookups
  • Handle exemptions as a second step: Create the customer, then attach exemptions through Create an exemption

Error Handling

Same policy for products and customers. Every error comes back in one envelope, { "code", "message", "requestId", "errors": [...] }, where errors holds one entry per request field that failed validation, each with a field, code, and message. Read it, then decide whether the failure is worth retrying.

RETRYABLE5xx · 429
Back off, then re-send
Send the identical payload. Creating a product or customer is idempotent on externalId and source, so a retry of a request that already landed returns the existing record with 200 rather than a duplicate.
NOT RETRYABLEOther 4xx
Fix the data, then re-send
The errors array names the offending field. Correct it, then re-send. Retrying unchanged will fail the same way.
Cap retries. Three attempts is plenty. After that, log the payload, the response, and its requestId, and surface it for a human rather than looping.

Common Failures

What actually goes wrong when creating products and customers:

  • Invalid category or subcategory: An unrecognized pair returns 400. Match values to List the product category catalog
  • Missing required fields: Products need externalId, name, productCategory, productSubcategory, and taxExempt
  • Duplicate externalId on update: Changing a product's externalId to one another product uses returns 409
  • Invalid address: Validate addresses before saving them
  • Missing or invalid API key: Every request needs a valid Api-Key header, or it returns 401

For the full status-code reference and retry strategies, see the Error Handling guide.

Integration Checklist

Before you wire up tax calculation or transaction sync:

  • Created product records for every item in your catalog
  • Verified products exist and carry the classification you expect
  • Created customer records for exempt entities (if applicable)
  • Attached exemptions to those customers and uploaded certificates (if applicable)
  • Tested product lookup in a sample tax estimate request
  • Tested customer lookup in a sample tax estimate request

Next Steps

With products and customers in place:

  1. Start calculating tax: Reference products in POST /tax-estimations requests. See the Sales Tax Calculations guide.

  2. Sync transactions: Reference products and customers in POST /transactions requests. See the Syncing Transaction Records guide.

  3. Handle updates: Set up workflows to push catalog and customer changes through to Kintsugi.

For endpoint-level detail, see: