The tax connection is your environment's link to the Quaderno account that calculates automatic sales tax. Each environment has one, with a tax_connection_id beginning with tcn_, and each sandbox keeps its own. A connection becomes active when the seller agrees to Flint's disclosure and you send POST /v1/tax-connection/connect. Until then, orders with automatic tax fail with 409 ACTION_NOT_ALLOWED and reason configuration_required instead of calculating zero tax. The error's details[0].resource_type is tax_connection.
GET /v1/tax-connection always returns 200, even before anything exists, so one read tells you whether automatic tax works, why not, and what connecting needs. Read it with settings.read; connect and disconnect with settings.write. Merchant API keys, CLI tokens, and Dashboard sessions can call these routes. Partner app install tokens can't. Live and sandbox environments are separate: a sandbox connection and its registrations never carry over to live, and the seller agrees to the disclosure again for live.
To turn on automatic tax:
- Set
tax.default_line_item_tax_categorythroughPATCH /v1/settingsto a category automatic tax supports, such asgeneralorphysical_goods. - Add a tax registration for each state where the seller collects tax.
- Read
GET /v1/tax-connection. Show the seller every statement indisclosure.statements. - After the seller agrees, send
POST /v1/tax-connection/connectwithdisclosure.version.
The Sales tax guide walks through the same flow with the order requests that follow. To turn automatic tax off later, send POST /v1/tax-connection/disconnect; see Disconnect.
Fields#
| Field | Description |
|---|---|
tax_connection_id | Present once a record exists, which happens at the first registration or the first connect. |
status | Whether automatic tax works. See Status. |
status_reason | Why a not_connected or attention_required connection is in that state, or null. See Status reasons. |
supported_actions | The actions you can take now: connect when POST /v1/tax-connection/connect can complete setup, disconnect when POST /v1/tax-connection/disconnect can turn automatic tax off, otherwise []. See Supported actions. |
business_name | The verified legal business name. Before connect it shows what connect will use. Once connected, it is what the tax account uses. Omitted when Flint has no verified business details. |
business_address | The verified business address, which is the tax origin. Same rule as business_name. |
disclosure | What the seller agrees to when you connect. See Disclosure and seller attestation. |
seller_attestation | {disclosure_version, attested_at} once a connect has recorded the seller's agreement, otherwise null. |
version | Present once a record exists. Increases whenever the connection or one of its registrations changes. |
created_at, updated_at | Present once a record exists. |
Status#
status is a closed set. Branch on it to decide whether automatic tax works.
status | Meaning |
|---|---|
not_connected | Automatic tax is off: it was never connected, or you turned it off. Registrations you add are saved as pending. |
provisioning | A connect is running now. Read again in a few seconds. |
active | Automatic tax calculates for every active registration. |
attention_required | Setup didn't finish, or the connection stopped working. Automatic tax fails with ACTION_NOT_ALLOWED and reason configuration_required until the status is active again. |
disabled | Flint support turned the connection off. Contact support to reconnect. |
A connection never stays in provisioning after a connect stops. If a connect is interrupted, the next read shows attention_required.
Status reasons#
status_reason explains not_connected and attention_required, and is null for the other statuses. It can gain values, so treat one you don't recognize as needing attention and read the connection again later. For not_connected, the reasons below are listed in the order you fix them, and null means connect can succeed.
status | status_reason | What to do |
|---|---|---|
not_connected | not_offered | Automatic tax isn't offered for this account or sandbox. Use external tax, or contact Flint support. |
not_connected | onboarding_required | The merchant has no verified business details yet. Finish onboarding, then read the connection again. |
not_connected | tax_category_required | tax.default_line_item_tax_category in settings isn't a category automatic tax supports. Update it with PATCH /v1/settings. |
not_connected | registration_required | Add at least one tax registration that is pending, scheduled, active, or rejected. ended registrations don't count. |
not_connected | disconnected | You turned automatic tax off with disconnect, and nothing else blocks connecting. Show the disclosure to the seller, then connect again. |
not_connected | null | Show the disclosure to the seller, then connect. |
not_connected, attention_required | temporarily_unavailable | Flint has paused automatic tax for every merchant. Retry later. Connect and registration writes return 503 SERVICE_UNAVAILABLE. |
attention_required | setup_incomplete | A connect didn't finish. Send POST /v1/tax-connection/connect again. |
attention_required | registration_rejected | The tax account rejected a registration. Fix or delete the rejected registration, then connect again. |
attention_required | business_details_changed | The verified business name, email, address, or currency changed after you connected. Contact Flint support to reconnect. Connect, disconnect, and registration writes return 409 ACTION_NOT_ALLOWED with reason reconciliation_required. This also applies after you disconnect. |
attention_required | support_required | Flint couldn't confirm the tax account was created. Contact Flint support with the request_id of the connect. Connect, disconnect, and registration writes return 409 ACTION_NOT_ALLOWED with reason reconciliation_required. |
active, provisioning | null | Nothing to fix. |
disabled | null | Contact Flint support to reconnect. |
Setting or clearing end_date on a scheduled or active registration works in every status, including those that refuse other registration writes.
After a disconnect, status_reason shows the first reason in the order above that still blocks connecting: not_offered, then onboarding_required, then tax_category_required, then registration_required. It shows disconnected only when none of them apply, so disconnected and null both mean connect can succeed.
business_details_changed and temporarily_unavailable are read-time states. Flint works them out when you read the connection and when it calculates tax, and it changes nothing it has stored. They don't advance version and send no webhook. When an order fails with ACTION_NOT_ALLOWED, reason configuration_required, and resource_type tax_connection, read the connection again instead of relying on your mirror of it.
status_reason and supported_actions can also change with the clock. A registration that reaches its end_date stops counting toward registration_required, so a connection that isn't connected can move to registration_required and lose connect from supported_actions without any write. Like the read-time states, this changes no version and sends no webhook. Read the connection before you show a connect control.
Supported actions#
supported_actions lists the actions that can succeed now. It can gain values, so ignore one you don't recognize.
| Action | Listed when |
|---|---|
connect | Connecting can complete setup: the status is not_connected with a null or disconnected reason, or attention_required with setup_incomplete or registration_rejected. |
disconnect | Disconnecting can turn automatic tax off: the status is active, or attention_required with setup_incomplete or registration_rejected. |
A connection in attention_required with setup_incomplete or registration_rejected lists both. In every other state supported_actions is [], including provisioning and disabled. It is also [] for a credential without settings.write, which can read the connection but not change it. Use it to decide which controls to show, and still handle the errors the routes return.
Disclosure and seller attestation#
disclosure holds the text the seller agrees to and its version:
{
"version": "2026-10-26.5f3a9c1e",
"statements": [
{"text": "I confirm this Flint merchant represents one independently liable legal seller and taxpayer. It does not share registrations with another seller, switch sellers by transaction, or use Flint as Merchant of Record."},
{"text": "I authorize Flint to create and configure a Quaderno account for automatic tax calculations. Flint does not file returns or remit tax through this connection."},
{"text": "Automatic tax costs 0.50% of each payment where Flint calculates tax above zero. Payments with tax you supply, or with no tax, have no automatic tax fee."}
]
}
Show every statement, in order, to the seller. Get their agreement to all of them before you send the version. Connecting turns on a billed service and creates a tax account that only Flint support can remove, so the agreement has to come from the seller or someone acting for them. Flint can't check that you showed the text, so sending the version is your statement that you did.
The third statement carries the merchant's own automatic tax rate, so the example rate above is illustrative. version is opaque: don't parse it. It changes whenever the text changes, including a rate change, so read the connection again shortly before you show the disclosure.
Send the version you showed in seller_attestation:
{"seller_attestation": {"disclosure_version": "2026-10-26.5f3a9c1e"}}
If the disclosure changed since you read it, the connect returns 409 ACTION_NOT_ALLOWED with details[0].reason disclosure_changed. details[0].resource_type is tax_connection, and details[0].current_disclosure_version is the version to show next. Read GET /v1/tax-connection, show the seller the current statements, get their agreement, and send the new version. The refusal changes nothing, and remediation.retry is none: the same request fails again until the seller has agreed to the new text.
Once a connect succeeds, seller_attestation holds the version agreed to and attested_at. Registration writes don't ask for the attestation again.
Connect#
POST /v1/tax-connection/connect creates or finishes the tax account from the merchant's verified business details, using tax.default_line_item_tax_category from settings as the account default. It then syncs every pending registration and returns the connection. The body takes seller_attestation (required) and expected_version (optional, the connection's version; a stale value returns 409 VERSION_CONFLICT with details[0].current_version). Send an Idempotency-Key so a retry resumes the same setup instead of starting another.
A connect the seller can fix returns 200 with the result in the connection's state, not an error:
status: "active"means automatic tax is on.status: "attention_required"withstatus_reason: "registration_rejected"means the tax account exists but rejected a registration. List the registrations, fix or delete eachrejectedone, then connect again. Connect takes no registrations in its body. An initial connect with a rejected registration doesn't goactive, because collecting tax without a state the seller says they're registered in would under-collect.
Connecting marks every open order that uses automatic tax, has calculated tax, and has no issued invoice as tax incomplete. The registrations decide where tax applies, so Flint recalculates those orders instead of keeping tax calculated under the old set. Update an order to recalculate it before taking payment.
Connect also turns automatic tax back on after a disconnect. Send a new seller_attestation for the current disclosure: the earlier agreement doesn't carry over. Connecting needs at least one registration that hasn't ended. It doesn't turn tax.default_enabled back on, so update settings if new orders should calculate tax again.
If the response is lost or the request times out, read GET /v1/tax-connection. status: "active" means it worked. provisioning means the connect is still running. attention_required with setup_incomplete means you can send it again. If a connect returns 502 EXTERNAL_SERVICE_ERROR, retry with the same Idempotency-Key; Flint resumes setup and never creates a second tax account. If Flint can't confirm that a tax account was created, the connect returns 503 SERVICE_UNAVAILABLE, the connection reads support_required, and Flint never retries the creation on its own. Contact support with the request_id.
curl -X POST https://api.withflintpay.com/v1/tax-connection/connect \
-H "Authorization: Bearer $FLINT_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: connect-tax-cedar-and-stone" \
-d '{
"seller_attestation": {"disclosure_version": "2026-10-26.5f3a9c1e"}
}'
{
"data": {
"tax_connection_id": "tcn_01KYB4W8M2R6T9XQ3N5HPZ7C0D",
"status": "active",
"status_reason": null,
"supported_actions": [],
"business_name": "Cedar & Stone LLC",
"business_address": {
"line1": "200 Spectrum Center Drive",
"city": "Irvine",
"state": "CA",
"postal_code": "92618",
"country": "US"
},
"disclosure": {
"version": "2026-10-26.5f3a9c1e",
"statements": [
{"text": "I confirm this Flint merchant represents one independently liable legal seller and taxpayer. It does not share registrations with another seller, switch sellers by transaction, or use Flint as Merchant of Record."},
{"text": "I authorize Flint to create and configure a Quaderno account for automatic tax calculations. Flint does not file returns or remit tax through this connection."},
{"text": "Automatic tax costs 0.50% of each payment where Flint calculates tax above zero. Payments with tax you supply, or with no tax, have no automatic tax fee."}
]
},
"seller_attestation": {
"disclosure_version": "2026-10-26.5f3a9c1e",
"attested_at": "2026-11-01T16:42:09Z"
},
"version": 1793551329,
"created_at": "2026-11-01T16:30:41Z",
"updated_at": "2026-11-01T16:42:09Z"
}
}
Disconnect#
POST /v1/tax-connection/disconnect turns automatic tax off. It needs settings.write. The body takes:
tax_default_behavior(required):keeporturn_off. It decides what happens to the Calculate tax on new orders setting,tax.default_enabled. A request without it returns400 INVALID_REQUESTwithREQUIRED.expected_version(optional): the connection'sversion. A stale value returns409 VERSION_CONFLICTwithdetails[0].current_version.
Send an Idempotency-Key. If the request fails partway, retrying with the same key finishes it.
tax_default_behavior never turns tax on. When tax.default_enabled is already false, both values leave settings alone. When it is true:
tax_default_behavior | What happens to tax.default_enabled | New orders, checkout sessions, and subscription renewals that inherit tax |
|---|---|---|
turn_off | Set to false. | Created without tax. Subscription renewals bill with no tax until you turn tax back on. |
keep | Stays true. | Refused with 409 ACTION_NOT_ALLOWED and reason configuration_required until you reconnect, set tax on each order yourself, or turn the default off. Subscription renewals are held and retried; the buyer isn't charged and dunning doesn't start. |
Choose turn_off to keep selling without tax. Choose keep when orders should wait for automatic tax to come back instead of going out untaxed.
What disconnecting does#
- The connection reads
statusnot_connectedwithstatus_reasondisconnected.supported_actionslistsconnectonce nothing else blocks it.versionadvances and Flint sendstax_connection.updated. - Open orders that use automatic tax, have calculated tax, and have no issued invoice become tax
incomplete. Fix them by turning tax off on the order, switching it to external tax, or reconnecting and updating the order. - Invoices with issued tax still collect. Their frozen tax doesn't change.
- Payments in flight for orders with automatic tax are refused, because those orders no longer have calculated tax.
- Payment links and checkout sessions that turn tax on explicitly with
tax.enabled: trueare refused when the checkout session is created, with either value oftax_default_behavior. - Registrations stay as they are, and the seller's tax account isn't closed, so you can reconnect later. While disconnected, registration writes are saved in Flint only.
Which states allow it#
| Connection | Result |
|---|---|
active | Disconnects. |
attention_required with setup_incomplete or registration_rejected | Disconnects. |
not_connected, including after a disconnect | 409 ACTION_NOT_ALLOWED with reason already_applied, or not_offered when automatic tax isn't offered for this account or sandbox. |
provisioning | 409 OPERATION_IN_PROGRESS. Retry after Retry-After. |
attention_required with business_details_changed or support_required | 409 ACTION_NOT_ALLOWED with reason reconciliation_required. Flint support has to act. |
disabled | 409 ACTION_NOT_ALLOWED with reason unsupported_state. |
When Flint has paused automatic tax for every merchant, the request returns 503 SERVICE_UNAVAILABLE. If another settings write is running, it returns 409 CONCURRENT_MODIFICATION; retry with the same Idempotency-Key.
Response#
The response is a DisconnectTaxConnectionResult with two members: tax_connection, the connection after the change, and settings, the merchant settings as GET /v1/settings returns them. Read settings.tax.default_enabled to see whether the request turned the default off.
curl -X POST https://api.withflintpay.com/v1/tax-connection/disconnect \
-H "Authorization: Bearer $FLINT_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: disconnect-tax-cedar-and-stone" \
-d '{
"tax_default_behavior": "turn_off",
"expected_version": 1793555700
}'
The example shows only some sections of settings.
{
"data": {
"tax_connection": {
"tax_connection_id": "tcn_01KYB4W8M2R6T9XQ3N5HPZ7C0D",
"status": "not_connected",
"status_reason": "disconnected",
"supported_actions": ["connect"],
"business_name": "Cedar & Stone LLC",
"business_address": {
"line1": "200 Spectrum Center Drive",
"city": "Irvine",
"state": "CA",
"postal_code": "92618",
"country": "US"
},
"disclosure": {
"version": "2026-10-26.5f3a9c1e",
"statements": [
{"text": "I confirm this Flint merchant represents one independently liable legal seller and taxpayer. It does not share registrations with another seller, switch sellers by transaction, or use Flint as Merchant of Record."},
{"text": "I authorize Flint to create and configure a Quaderno account for automatic tax calculations. Flint does not file returns or remit tax through this connection."},
{"text": "Automatic tax costs 0.50% of each payment where Flint calculates tax above zero. Payments with tax you supply, or with no tax, have no automatic tax fee."}
]
},
"seller_attestation": {
"disclosure_version": "2026-10-26.5f3a9c1e",
"attested_at": "2026-11-01T16:42:09Z"
},
"version": 1793556600,
"created_at": "2026-11-01T16:30:41Z",
"updated_at": "2026-11-01T18:10:00Z"
},
"settings": {
"settings_id": "set_01KEZF7C00J81795CYBW13GTQH",
"merchant_id": "mer_01KEZF7C00MPP0T9S0KH5BB4CC",
"tax": {
"default_enabled": false,
"default_line_item_tax_category": "physical_goods"
},
"version": 4,
"created_at": "2026-03-17T14:30:00Z",
"updated_at": "2026-11-01T18:10:00Z"
}
}
}
Turn automatic tax back on#
Reconnect with POST /v1/tax-connection/connect and a new seller_attestation. Read GET /v1/tax-connection first: the disclosure may have changed, and status_reason says whether anything else blocks connecting. Connect checks the business details, syncs the registrations again, and returns active. It doesn't turn tax.default_enabled back on.
If the response is lost, read GET /v1/tax-connection. status: "not_connected" with status_reason: "disconnected" means the disconnect finished. If it still reads active, send the request again with the same Idempotency-Key; without a key, send the same body again. The settings change may already have happened, which is safe: orders that inherit tax are then created without it, as turn_off asks.
Read the connection#
curl https://api.withflintpay.com/v1/tax-connection \
-H "Authorization: Bearer $FLINT_SECRET_KEY"
This example is a sandbox that has one saved registration and hasn't connected. The connection record exists because the registration created it. Before any registration or connect, tax_connection_id, version, created_at, and updated_at are absent and status_reason is registration_required.
{
"data": {
"tax_connection_id": "tcn_01KYB4W8M2R6T9XQ3N5HPZ7C0D",
"status": "not_connected",
"status_reason": null,
"supported_actions": ["connect"],
"business_name": "Cedar & Stone LLC",
"business_address": {
"line1": "200 Spectrum Center Drive",
"city": "Irvine",
"state": "CA",
"postal_code": "92618",
"country": "US"
},
"disclosure": {
"version": "2026-10-26.5f3a9c1e",
"statements": [
{"text": "I confirm this Flint merchant represents one independently liable legal seller and taxpayer. It does not share registrations with another seller, switch sellers by transaction, or use Flint as Merchant of Record."},
{"text": "I authorize Flint to create and configure a Quaderno account for automatic tax calculations. Flint does not file returns or remit tax through this connection."},
{"text": "Automatic tax costs 0.50% of each payment where Flint calculates tax above zero. Payments with tax you supply, or with no tax, have no automatic tax fee."}
]
},
"seller_attestation": null,
"version": 1793550641,
"created_at": "2026-11-01T16:30:41Z",
"updated_at": "2026-11-01T16:30:41Z"
}
}
Registrations and an active connection#
While the connection isn't active, registration writes are saved in Flint only, as pending, and never create the billed tax account. Connect is the one route that completes setup. Once the connection is active, creating a registration syncs it in the request. See Tax registrations for the rules.
Webhook#
tax_connection.updated sends the connection, the same object GET /v1/tax-connection returns. It fires on every committed change to the connection or its registrations: when the record starts, on every registration create, edit, delete, or sync, when a connect finishes or fails, when a disconnect finishes, and when Flint support disables the connection. Compare version with the connection you stored and keep the higher one. The read-time states business_details_changed and temporarily_unavailable send nothing. A registration's status changing because a start or end date passed sends nothing either. Registrations have no events of their own: list them again after this event.
