KintsugiKintsugi
API reference / Changelog

API changelog

Every change to the Kintsugi API and every release, newest first. Breaking changes carry the Breaking change mark. Tenanted releases are dated, and you choose one with the Api-Version header.

Changes

2026-10-07

Breaking changes
Limits billing changes to an organization's Owners, Admins and own API keyBreaking change
Breaking changeBilling

POST /billing/checkout, POST /billing/portal-session, PATCH /billing/plan and PATCH /billing now accept only the organization's own Owners and Admins and the organization's own API key. Any other caller that can reach the organization, including a Member, a partner user and a portfolio or reseller key, answers 403 with code forbidden; these callers were accepted before. A user acting through a portfolio is refused the same way. An organization you cannot access still answers 404, and the billing read endpoints are unchanged.

What to do
  • Start checkouts, portal sessions and plan changes from the organization's own Owner or Admin session, or with its organization API key.
  • Handle 403 with code forbidden from these four endpoints.
Reports access to each capability on billing details
NewBilling

GET /billing/details now fills in capabilities: one { capability, access } entry per capability, where access is INCLUDED, NOT_INCLUDED (the organization is on a paid plan that does not include it) or REQUIRES_PAID_PLAN (it needs a paid plan first). capabilities is null when access does not depend on per-capability settings for the organization, so gate on effectiveEntitlement instead. New capabilities and access values can be added, so treat an unknown access as not included and ignore a capability you do not gate on.

What to do
  • Read capabilities to show or hide a feature, and fall back to effectiveEntitlement when it is null.
Updates an organization's bank details
NewOrganizations

PATCH /bank-details changes bankName, accountNumber, accountType (CHECKING or SAVINGS), accountHolderName and routingNumber. A field you leave out keeps its stored value, a field sent as null is cleared, and the record is created when none exists. Text values are trimmed and cannot be blank, bankName holds up to 100 characters, and an unknown field is refused. The response never returns full numbers: accountNumberLast4 and routingNumberLast4 hold the last four characters, or **** for a number of four characters or fewer, and are null when not captured. A user must be an Owner or Admin of the organization; an API key that reaches the organization is accepted. Select the organization with Organization-Id, Connection-Id or Entity-Id.

What to do
  • Send only the fields you want to change, and null for one you want to clear.
Recalculates a filing's amounts on request
NewFilings

POST /filings/{filing_id}/recalculate queues a recalculation of a filing's amounts from its transactions and answers 202 with filingId, countryCode and status QUEUED. The recalculation runs in the background, so read the filing again for the new amounts. Only US and Canadian filings that are not in FILING, SUBMITTED or FILED status can be recalculated; others answer 400 with code invalid_request. It is available only for organizations that file their own returns, to their Owners and Admins, their API keys and the portfolio that manages them; other callers receive 403. A filing that does not exist or belongs to an organization you cannot access answers 404. Send an Organization-Id, Connection-Id or Entity-Id selector to say which organization the filing belongs to.

What to do
  • After the 202, read GET /filings/{filing_id} to see the recalculated amounts.

2026-10-06

Lists active connections first by default
Behavior changeConnections

When you leave out sort, GET /connections now lists ACTIVE connections first, then INACTIVE, then CONNECTION_ERROR, with the most recently updated first within each status. Before, it listed connections in id order. An explicit sort and order work as before. A cursor issued without sort before this change answers 400 with code stale_cursor.

What to do
  • If your integration depends on id order, send a sort instead.
  • On stale_cursor, start again from the first page without a cursor.
Narrows the connections list to one organization on request
Behavior changeConnections

GET /connections now honors the Organization-Id, Connection-Id and Entity-Id headers, with Entity-Source to disambiguate an Entity-Id, and lists only that organization's connections. Before, these headers were ignored and the list always covered every organization your credential can access, which is still what you get without them. An organization outside your access answers 404. A cursor works only with the selector it came from: reusing it with another answers 400 with code stale_cursor.

What to do
  • Stop sending a selector header to GET /connections if you expect every organization's connections.
  • Send Organization-Id to list one organization's connections without filtering them yourself.
Returns each connection's first and latest transaction dates
NewConnections

Connections from GET /connections and GET /connections/{conn_id} now include minDate and maxDate: the dates, as YYYY-MM-DD, of the earliest and latest transaction that GET /transactions returns for the connection. Both are null for a connection with no transactions. Other endpoints that return a connection, such as connect, activate and deactivate, return null for both.

Finds the organizations connected to a Shopify store
NewConnections

GET /connections/shopify/organizations takes a shopId, the store name such as mystore or the full mystore.myshopify.com, matched without regard to letter case. It returns one { organizationId } per organization you can access that has an ACTIVE connection to the store, ordered by organizationId, and an empty list when none does, including for a blank shopId. Inactive and failed connections are left out. It searches every organization your credential can access, or one when you send Organization-Id, Connection-Id or Entity-Id; an organization outside your access answers 404.

What to do
  • Call it after a store connects to find which of your organizations it belongs to.
Filters transactions by customer
NewTransactions

GET /transactions and GET /transactions/summary accept customerId, 1 to 255 characters, which keeps only the transactions of the customer with that exact id. It combines with the other filters. A customer id that is unknown or belongs to an organization outside your access returns an empty page and a summary with zero count, never an error. The summary's incompleteAddressCount ignores every filter, this one included. A cursor works only with the customerId it came from: reusing it with another answers 400 with code stale_cursor.

What to do
  • Send customerId to list one customer's transactions without searching by name.
Returns a US filing's tax liability by local jurisdiction
NewFilings

GET /filings/{filing_id}/enhanced-data returns a US filing's actual tax liability: summary totals, jurisdictionBreakdown per local jurisdiction, deductions and exemptions, refunds, and state-specific breakdowns such as county groupings and use tax where the state reports them. Send the filing's two-letter state as jurisdiction. The data is built on request. When it is ready the response is 200 with status DONE and the data in data; while it builds the response is 202 with IN_PROGRESS, so poll until it is DONE. A failed build answers 200 with FAILED and is not retried until you ask. A filing with no transactions answers DONE with data set to null. POST /filings/{filing_id}/enhanced-data/rebuild starts a fresh build even when one is stored and answers 202, or reports the build already running. Rebuilding is open to the same callers as PUT /filings/{filing_id}/submission, for organizations that file their own returns; others receive 403. It allows 10 requests a minute per portfolio, or per organization outside a portfolio, then answers 429. Both endpoints answer 404 for a filing you cannot access, 400 with code invalid_request when jurisdiction does not match the filing, and 400 with the new code unsupported_jurisdiction for a state without this data.

What to do
  • Poll the GET endpoint while it answers 202, and call rebuild after a FAILED result.
  • If your client accepts only known error code values, add unsupported_jurisdiction.
Generates a VAT return report as a report job
NewReports

POST /reports/jobs accepts reportType VAT_REPORT with reportArgs.filingId, for a VAT filing in Germany, the United Kingdom, the Czech Republic, Spain or Singapore. Once the job is READY, GET /reports/jobs/{report_job_id}/result returns it as JSON in vatReport: the return form's boxes grouped in sections, each with its boxCode, boxLabel, amount, category and sourceTransactionCount; a summary of output VAT, input VAT, net VAT payable and sales and purchases excluding VAT; ossDestinations, the VAT owed per EU destination country, or null when there are no cross-border sales; and limitations. Amounts are decimal strings rounded to the cent. Creating the job answers 404 for a filing outside the organization, 400 for another country, for a filing with more than 100,000 transactions or with deliveryMethod EMAIL, and 403 for an organization without this feature enabled. The result endpoint answers 400 for a report you download instead, 409 before the job is READY and 404 for a job you cannot access.

What to do
  • Start the job, poll GET /reports/jobs/{reportJobId} until it is READY, then read the report from the result endpoint.

2026-10-05

Breaking changes
Limits pausing a filing to the window before its due dateBreaking change
Breaking changeFilings

Pausing a filing now follows the same window as the Kintsugi app. After the 15th of the month a filing is due, it can no longer be paused with any pauseIntent, including assistance and skip, which were accepted before. A review pause also needs a pausedUntilDate from today through the 15th of the due month; a date in the past, accepted before, is now refused. A date that is still today anywhere in the world counts as today. POST /filings/{filing_id}/pause answers a refused pause with 400 and code invalid_request, and POST /filings/bulk/pause still answers 200, listing each refused filing in failed.

What to do
  • Pause a filing no later than the 15th of the month it is due.
  • For a review pause, send a pausedUntilDate no earlier than today and no later than the 15th of the due month.
  • After a bulk pause, check failed for filings that were not paused.
Returns already_connected when an activated store is already connectedBreaking change
Breaking changeConnections

When POST /connections/apideck/{conn_id}/activate finds that the shopId belongs to a connection your organization already has, it reactivates that existing connection, removes the one being activated, and answers 400 as before. The error code is now already_connected instead of invalid_request, with the message This store is already connected. The existing connection was reactivated. The store is connected and nothing more is needed. already_connected is a new value of the code field in error responses.

What to do
  • Treat 400 with code already_connected as success, and list your connections again to find the one that remains.
  • Stop relying on invalid_request to detect an already connected store.
  • If your client accepts only known code values, add already_connected.
Accepts NetSuite and DualEntry identifiers when activating a connection
NewConnections

POST /connections/apideck/{conn_id}/activate accepts three optional fields: netsuiteAccountId and netsuiteSubsidiaryId for a NetSuite connection, and dualentryCompanyId for a DualEntry connection. They are saved in the same step to the connection that remains active, which is your existing connection when the shopId matches one. A field you leave out keeps its saved value, and null or an empty string for dualentryCompanyId covers every company. Other sources ignore these fields.

What to do
  • Send these identifiers with the activate call, so they are saved on the connection that remains.
Deletes a connection that was never finished
NewConnections

DELETE /connections/apideck/{conn_id} cleans up a connection left behind when the connect flow was cancelled or failed before activation, and answers 204. It removes only a connection with status INACTIVE or CONNECTION_ERROR that holds no data, and the removal is permanent. An active connection, or one that already holds data such as transactions, is kept and the call answers 409 with code conflict, so it is safe to call even when you did not see how an activation ended. A connection that does not exist or is not yours answers 404, as does a repeat call after a successful delete. A credential for more than one organization must name one with Organization-Id, Connection-Id or Entity-Id, or receives 400.

What to do
  • After a cancelled or failed connect flow, call this endpoint, and keep the connection when it answers 409.
Records the confirmation and documents for a return you file yourself
NewFilings

Two endpoints let an organization that files its own returns record each one in Kintsugi. PUT /filings/{filing_id}/submission takes an action of save_draft, which saves the confirmation details and keeps the filing in FILING, or mark_filed, which saves them and marks the filing FILED. Its optional fields are returnConfirmationId, paymentConfirmationId, amountAdjusted, amountFees, amountPenalties and amountDiscounts, with amounts as decimal strings; a field you leave out is unchanged and a field sent as null is cleared. It returns the updated filing, or 409 when the filing is not in FILING. PUT /filings/{filing_id}/artifacts/{artifact_type} stores the RETURN or PAYMENT confirmation as a PDF of up to 10 MB, sent in the file part of a multipart/form-data body, replacing any document already stored for that type. It answers 409 unless the filing is FILING or FILED, 413 for a larger file and 422 for a file that is not a PDF. Both endpoints are available only for organizations set up to file their own returns, to their Owners and Admins, their API keys and the portfolio that manages them; other callers receive 403.

What to do
  • Upload the return and payment confirmations while the filing is in FILING, then call the submission endpoint with action: "mark_filed".
  • Download a stored document with GET /attachments/{attachment_id}/download.
Saves California and Idaho portal access codes with registration credentials
NewRegistrations

PUT /registrations/{registration_id}/credentials accepts jurisdictionSpecificFields: cdtfaThirdPartyAccessSecurityCode for a California registration and accessCode for an Idaho registration, named as in the credentials reveal response. Only the keys you send change, and leaving the field out or sending null keeps the saved values. The codes are stored encrypted, are never returned by this endpoint, and can be read back with POST /registrations/{registration_id}/credentials/reveal. An unsupported key or a blank value answers 422. A code for a different state than the registration's answers 400, as does a registration that does not yet have its business name, registration type and sales tax id on file.

Adds filing metrics to the organizations list
NewOrganizations

GET /organizations accepts expand=filingMetrics, which adds a filingMetrics object to each organization: totalFilings, filed, pendingApproval (unfiled returns not yet past their due date), overdue (unfiled returns past their due date) and liabilityByCurrency, one { currency, amount } entry per currency. Returns marked do not file are left out of pendingApproval and overdue. Currencies are listed in code order, with returns that have no currency yet last as currency: null, and amounts are decimal strings that are never converted between currencies. An organization with no returns gets zero counts and an empty liabilityByCurrency. Without expand the response is unchanged, an unknown expand value answers 422, and a cursor stays valid with or without expand.

What to do
  • Pass expand=filingMetrics to get each organization's filing status in the same call as the list.

2026-10-02

Breaking changes
Applies one access rule to every address changeBreaking change
Breaking changeAddresses

The five endpoints that change addresses now accept the same callers: any member of the organization, a portfolio Owner or Admin who manages it, or an API key for the organization. A portfolio Member now receives 403 with code forbidden from POST /addresses/approve, which accepted them before. The other four, which accepted only the organization's Owner among its own users, now also accept its Admins and Members, who received 403 before. An organization you cannot access still answers 404, and API keys work on all five as before.

What to do
  • Have a portfolio Owner or Admin approve addresses, or approve with the organization's API key.
  • Handle 403 with code forbidden from POST /addresses/approve.
  • If your app hid these address actions from organization Admins and Members, you can now offer them.
Requires a non-empty userId for imports started with an API keyBreaking change
Breaking changeImports

On POST /imports/initiate and POST /imports/upload-urls, whether you send userId now depends on your credential. With an API key it is required and must not be blank: a missing userId answers 400 with code invalid_request instead of 422, and an empty or whitespace-only value, accepted before, is now refused the same way. With a signed-in user session, userId is optional and ignored, and the import is recorded under that user.

What to do
  • With an API key, always send a non-empty userId that names the person or process uploading.
  • Expect 400 with code invalid_request, not 422, when userId is missing.
Lists imports newest first
Behavior changeImports

GET /imports now lists imports by createdAt, newest first, so a new upload appears on the first page. Before, the order did not follow upload time. Paging stays stable while new imports arrive, and a cursor issued before this change answers 400 with code stale_cursor.

What to do
  • Expect the newest import first.
  • On stale_cursor, start again from the first page without a cursor.
Lists transactions with a blank or invalid address
NewTransactions

GET /transactions/blank-addresses lists transactions that have no address yet, newest first, and GET /transactions/invalid-addresses lists those whose address needs fixing. Both cover committed transactions, including partially and fully refunded ones, dated on or after 2018-01-01, and leave out marketplace transactions. They span every organization you can access, or one when you send Organization-Id, Connection-Id or Entity-Id. Pages use limit (1 to 100, default 50) and cursor, and search matches the transaction id, externalId, externalFriendlyId, description and customer name. The invalid list can also be sorted with sort (usFirst by default, countryAsc or countryDesc) and filtered with country, hasCountry, hasState, hasCity, hasCounty, hasPostalCode and addressNotEmpty, which apply to the invalid addresses themselves; an unsupported country answers 400. A cursor works only with the sort, filters and organization it came from: reusing it with others answers 400 with code stale_cursor.

What to do
  • On stale_cursor, request the first page again without cursor.
Returns your customer id on transactions
NewTransactions

Transactions now include customerExternalId, your own identifier for the customer, beside customerId and customerName. It is null when the transaction has no customer or the customer has no external id. A list can now show your customer ids without a second call.

Emails a report's download link on request
NewReports

POST /reports/jobs accepts an optional deliveryMethod. DOWNLOAD, the default, works as before: fetch the report from GET /reports/jobs/{reportJobId}/download once it is READY. EMAIL sends a download link, when the report is ready, to the email address of the user behind your credential; the request cannot name another address. The response echoes deliveryMethod. EMAIL answers 400 for BULK_FILING_REPORTS, for an organization with email turned off, and for a credential without an email address.

What to do
  • Send deliveryMethod: "EMAIL" to receive the report link by email.
Counts your imports
NewImports

GET /imports/summary returns total, the number of imports across every organization your credential can access, or one organization when you send a selector such as Organization-Id. Archived imports are counted, so total can be higher than the number GET /imports lists.

Keeps an address's unincorporated setting when an edit leaves it out
FixAddresses

Editing a transaction address through PUT /addresses/transactions or PATCH /transactions/{transaction_id}/addresses used to reset its unincorporated setting to false, so changing only a street or postal code could apply city tax rates to an address outside city limits. Both endpoints now accept an optional isUnincorporated: true keeps city rates from applying, false clears the setting, and leaving it out or sending null keeps the saved value. A new address created by an edit is saved as false when the field is left out. Changing only this setting on a verified address keeps it verified and recalculates the transaction's tax. Transaction addresses in responses now include isUnincorporated.

What to do
  • Review isUnincorporated on addresses you edited through these endpoints before this fix.
  • Send isUnincorporated only when you mean to change it.
Checks the length of a pause reason before pausing
FixFilings

A pauseReason is saved in a note on the filing, and the note, including a short label Kintsugi adds, holds up to 500 characters. A longer reason used to fail with a server error, sometimes after the filing was already paused. It now answers 400 with code invalid_request before anything changes, and POST /filings/bulk/pause refuses the whole request.

What to do
  • Keep pauseReason comfortably under 500 characters.

2026-09-30

Requests back filings for past periods through v1
NewFilings

Three v1 endpoints, now documented, let you ask Kintsugi to file returns for periods before your regular filing schedule began. GET /v1/filings/back-filing-request/options lists each active US registration with the periods it can request: periods that have ended, start on or after the registration's tax collection start, end before its first regular filing period, and are not already covered by a filing. Each period has a start_date, an end_date and a label, and each registration a remittance_tag (Paid with return, State bills you later or null) saying how the state collects penalties and interest. POST /v1/filings/back-filing-request takes requests, each a registration_id with its periods, and optional notes. It creates one BACK_FILING filing with status UNFILED per period and returns them in created_filings, with their count in total_created, and 200. Every period is checked before anything is created, so one invalid period refuses the whole request with 400. An unknown registration_id answers 404, an organization without a payment method on file receives 402, and an organization without back filing available receives 404 from both request endpoints. Nothing is filed until each filing is approved with the current terms from GET /v1/filings/back-filing-terms/current, which returns their id, version and text.

What to do
  • Request only the start_date and end_date pairs that the options endpoint lists for each registration.
  • Show the current terms to the person approving, then approve each filing with PUT /v1/filings/{filing_id}/approve, sending the terms id as back_filing_terms_id. Missing or outdated terms answer 400.
Adds partial exemptions for California partial exemption certificates
NewExemptions

A new exemption type, partial, records a customer's California partial exemption certificate, such as the one for qualifying manufacturing and research equipment. Unlike a full exemption, a sale to that customer stays taxable: Kintsugi applies the reduced rate the certificate allows to qualifying items sold in California. Create one with exemption_type partial and a certificate_type on POST /v1/exemptions, or with exemptionType and certificateType on POST /exemptions and POST /exemptions/bulk in the 2026-07-21 release. The certificate type is the form's code from GET /v1/exemptions/partial-certificate-types, which lists each form's code, name, jurisdiction and country; its optional jurisdiction parameter narrows the list to one state, ignoring case. Today the list holds nine California forms. A partial exemption needs country US and jurisdiction CA, and the certificate type is accepted only on a partial exemption. Exemptions, including those embedded in transactions, now report the certificate type, which is null for every other type. Partial exemptions are available to organizations that have them enabled; for any other organization, creating one answers 400.

What to do
  • Send exemption_type: "partial", a certificate_type from the forms list, country_code: "US" and jurisdiction: "CA" on v1, or the same fields in camelCase on the 2026-07-21 release.
  • Expect a missing or unknown certificate type, another country or another state to be refused: 422 on v1, 400 with code invalid_request on the 2026-07-21 release.

2026-09-22

Breaking changes
Requires a request body to deregister a registrationBreaking change
Breaking changeRegistrations

Deregistering now records when and why the registration closes. POST /v1/registrations/{registration_id}/deregister requires a JSON body with closure_date, reason and final_return_acknowledged, so a request with no body, which succeeded before, is now rejected with 422. The 2026-07-21 release changed the same way, with the fields closureDate, reason and finalReturnAcknowledged.

What to do
  • Send closure_date: the date the registration closes with the jurisdiction, as YYYY-MM-DD. Past and future dates are accepted.
  • Send reason: FULL_BUSINESS_CLOSURE or CLOSING_NEXUS_IN_STATE.
  • Send final_return_acknowledged: true, confirming that a final return is still owed.
Returns the existing credit note when a credit note is created twiceBreaking change
Breaking changeTransactions

Creating a credit note with an external_id already used for a credit note on the same original transaction used to fail with 409 Conflict. It now succeeds and returns the stored credit note unchanged. On v1 that response is 200, and creating a new credit note now answers 201 instead of 200. On the 2026-07-21 release the stored credit note comes back with 202, the same as a new one. The same external_id against a different original transaction still returns 409.

What to do
  • On v1, treat both 201 and 200 as success.
  • Stop relying on 409 to detect an existing credit note.
  • To change an existing credit note, update it: PUT /v1/transactions/{original_transaction_id}/credit_notes/{credit_note_id} on v1, or PATCH /transactions/{transaction_id} on the 2026-07-21 release.

2026-09-21

Breaking changes
Returns the existing record when a physical nexus is created twiceBreaking change
Breaking changeNexus

Creating a physical nexus for an organization, country, state and category that already has one used to fail with 409 Conflict. It now succeeds and returns the stored record unchanged: the dates and address in the request are not applied. On v1 and the Partner API the response is 200, the same as for a new record. On the 2026-07-21 release it is 200 for an existing record and 201 for a new one.

What to do
  • Stop relying on 409 to detect an existing record.
  • To change an existing record's dates or address, update it: PUT /v1/nexus/physical_nexus/{physical_nexus_id} on v1, or PATCH /physical-nexus/{physical_nexus_id} on the 2026-07-21 release.

2026-09-17

Breaking changes
Returns the existing product when a product is created twiceBreaking change
Breaking changeProducts

Creating a product with an external_id and source that already exist used to fail with 409 Conflict. It now succeeds with 200 and returns the stored product unchanged: the fields in the request are not applied. A product you deleted is restored instead, with status PENDING, and is not used in tax calculation until it is approved again. Creating a new product now answers 201 instead of 200.

What to do
  • Treat both 201 and 200 as success.
  • Stop relying on 409 to detect an existing product.
  • To change an existing product, update it with PUT /v1/products/{product_id}.

Releases

2026-10-06

LatestTenanted API

Consistent country codes, empty values and status casing

2026-10-06 makes the Tenanted API more predictable to build on. Every country code is now countryCode, a value that is not set is null rather than an empty string, and status and type values are uppercase throughout. Integrations pinned to 2026-07-21 are unaffected until you choose to move, and the table below lists everything that changes when you do.

Compared with 2026-07-21

ChangeApplies to2026-07-212026-10-06
Country code fieldAddresses, customers, transactions, tax estimates and billing usage, in requests and responsescountrycountryCode. Query parameters named country keep their name.
State on nexus exposure eventsGET /nexus/{nexus_id}/exposure-historystatestateCode, always present
State of a country-level filingstateCode and stateName on every filingEmpty stringnull
Organization state and country when not setstate and countryCode on organizations, and state in organization listsEmpty string, always presentnull, and may be omitted
Exemption with no match on a certificate uploadexemptionId on missing-certificate upload resultsEmpty stringnull
Missing-certificate status valuesUpload, upload status and public confirm-upload resultsLowercase: processing, satisfied, rejectedUppercase: PROCESSING, SATISFIED, REJECTED, plus PENDING on the public landing page
Exemption type when approving certificate importsPOST /certificate-imports/{certificate_import_id}/approve and POST /certificate-imports/bulk-approveAny text, defaulting to customerCUSTOMER, WHOLESALE, TRANSACTION or REVERSE_CHARGE, defaulting to CUSTOMER. Any other value returns 422.
Security questions in a credential revealfields on POST /registrations/{registration_id}/credentials/revealsecurity_questionssecurityQuestions
Line item with no product nameproductName on transaction linesnullEmpty string
Help article label on back-filing optionsGET /filings/backFilingRequest/optionsText or nullAlways text
Job ID on bulk filing resultsPOST /filings/bulk/approve and POST /filings/bulk/pausejobId, always nullRemoved

Moving an integration from 2026-07-21

  1. Send Api-Version: 2026-10-06 with each request you move. Requests without it keep running against 2026-07-21.
  2. Rename country to countryCode wherever you send or read an address, customer, transaction or tax estimate.
  3. Treat null as "not set" for filing and organization locations and for an unmatched exemptionId, and an empty productName as "no product name".
  4. Compare missing-certificate statuses in uppercase, and send exemptionType in uppercase when you approve certificate imports.
  5. Read stateCode on nexus exposure events, request securityQuestions when you reveal registration credentials, and stop reading jobId on bulk filing results.

2026-07-21

Tenanted API

Introducing the Tenanted API

A fresh foundation for building on Kintsugi. The Tenanted API is versioned by date, and you choose the release you build against. It speaks one consistent dialect from end to end: clean paths, camelCase fields, exact decimal amounts, and a single error format. And it is built for scale, with one credential reaching every organization it owns.

Compared with v1

Topicv12026-07-21
VersioningThe /v1 path prefixThe Api-Version header, dated YYYY-MM-DD. Optional; without it, a request runs against the launch release
Paths/v1/transactions/transactions
Authenticationx-api-keyApi-Key. Endpoints that manage people accept a signed-in session token instead
Choosing an organizationx-organization-id, required on almost every requestOne credential reaches every organization it owns. Narrow a request with Organization-Id, Connection-Id, or Entity-Id
Field namessnake_casecamelCase
Money and ratesDecimal strings out, numbers or strings inDecimal strings at a fixed scale: amounts to 2 places, rates to 9
TimestampsNo single documented formatRFC 3339 in UTC, always ending in Z
ErrorsShapes vary by endpointOne envelope everywhere: code, message, requestId, and errors
PortfolioNot availablePortfolio and Portfolio Reseller endpoints, for partner accounts

Moving an integration from v1

  1. Send Api-Version with every request, pinned to the release you build against (the newest is 2026-10-06), so your integration stays on it until you decide to move.
  2. Drop /v1 from your paths.
  3. Send your key as Api-Key, and replace x-organization-id with Organization-Id wherever you target one organization.
  4. Rename fields to camelCase, and read every amount as a decimal string, never a float.
  5. Branch on the error code, not the message or the HTTP status, and quote requestId when you contact support.

Creating a transaction answers 202 Accepted straight away, and tax is calculated just after. Check processingStatus before you read the tax totals.

v1

Legacy

The original Kintsugi API, versioned in the path. It is documented exactly as before, so existing integrations can keep running with confidence. New integrations should start on the newest Tenanted API release.

Kintsugi API Changelog