Logic API developer guide
How the Logic API is designed to work, in detail. Use it to evaluate fit and plan an integration.
How the Logic API is designed to work, in detail. Use it to evaluate fit and plan an integration. Read the sections that apply to you, in any order.
Planned interface. The Logic API isn't available yet. The names, options and code in this guide reflect the current design and prototype, and may change. Code samples are shown in React because that's where the prototype is furthest along. The same concepts apply on iOS and Android. See Development status.
Authentication
Every request to Taxbit needs a short-lived token for the user filling in the form. In production, your server creates that token, never your frontend code:
- Your server uses your Taxbit credentials to create an account-owner bearer token for the signed-in user. It's the same token the current Taxbit React SDK uses.
- Your page fetches that token from your server and passes it as
bearerToken. - When it expires, fetch a new one and pass it in. The client library uses the latest value.
const { token } = await fetch('/api/taxbit-token').then((r) => r.json());
<TaxbitQuestionnaire config={{ questionnaire: 'W-9', locale: 'en-US', bearerToken: token, ... }} />Your Taxbit client secret stays on your server. The browser only ever gets the short-lived token.
When is a form done?
Every response from Taxbit includes complete: true or false. A form marked complete: true has no errors, and Taxbit won't come back to you later with a problem about it. The client library only submits when it sees that flag, so you never need to check completeness yourself.
onSuccess fires once, with one of two results:
| Result | What it means | What to do |
|---|---|---|
ingested | Taxbit stored the complete form. | Save document.document_id and show a confirmation. |
fragment | You asked only some questions. Those answers are valid, but the full form isn't complete yet. | Save data. It's been validated, but it isn't stored at Taxbit. |
If you leave out onSuccess, the client library shows its own "All done" screen. Always handle onError too. { kind: 'network' } means the connection dropped and you can retry. { kind: 'rejected' } shouldn't happen, so please report it to Taxbit.
Choosing the form
For W-forms, start with questionnaire: 'W-FORM'. You don't need to know ahead of time which form a person owes. Taxbit asks a few short questions first, such as whether they're a US person and whether they're an individual or a business, and then takes them to the right form: W-9, W-8BEN, W-8BEN-E or W-8IMY. One setup covers every user.
config={{ questionnaire: 'W-FORM', locale: 'en-US', bearerToken: token }}Naming a specific form (optional)
questionnaire also accepts a specific form. If every user you collect from needs the same one (for example, you only collect W-9s), you can name it. Taxbit then skips the questions that form already answers.
| If you collect… | questionnaire |
|---|---|
| Any W-form. You don't need to know which one. Recommended | W-FORM |
| Only if every user needs the same form: | |
| Only W-9s (US persons) | W-9 |
| Only W-8s (non-US persons) | W-8 |
| Only W-8BEN (non-US individuals) | W-8BEN |
| Only W-8BEN-E (non-US entities) | W-8BEN-E |
| Only W-8IMY (intermediaries) | W-8IMY |
When you name a specific form, don't also send the answers it already covers. For example, don't send "US person: yes" with W-9. Taxbit returns a 400 error if you do, so each answer has only one source.
Other documentation
Self-certification and DPS declarations aren't W-forms, so they have their own values: SELF-CERT and DPS.
In the prototype, the W-9 and self-certification paths work end to end. The W-8 paths from W-FORM, and DPS, are in development. See Development status.
Pre-fill what you know
If you already have some of the person's information, pass it in seed. The fields appear already filled in, and the user can correct them.
seed: {
account_holder: {
name: 'Acme LLC',
address: { country: 'US', first_line: '1 Market St', city: 'San Francisco',
state_or_province: 'CA', postal_code: '94105' },
},
},Seeded values are checked just like typed ones. The field names use the same snake_case format Taxbit stores forms in. For a returning user, prefill: 'latest' starts from their most recent submission instead, and your seed values still take priority.
Ask only some questions
Sometimes you only need part of a form, or you want to show information without letting the user change it. Three options handle this:
| Option | Effect |
|---|---|
collect | Show only these parts. |
skip | Show everything except these parts. |
locked | Show these parts, but don't let the user change them. |
The parts you can name are: documentType, classification, details, residencies, address and signing.
Two common setups
Review and sign. You already have the W-9 data and only need a signature:
{ questionnaire: 'W-9', seed: { /* their data */ }, locked: ['details'], collect: ['signing'] }Residencies only. You only need tax residencies, returned to you as a fragment:
{ questionnaire: 'SELF-CERT', seed: { account_holder: { is_individual: true } }, collect: ['residencies'] }Use locked, not skip, for anything the user is signing. People shouldn't have to certify information they can't see.
Optional question groups
The fatca, treatyClaims and typesOfIncome options turn extra groups of questions on or off. They work the same way as in the Taxbit React SDK.
Your Taxbit plan decides what's available. These options can turn a group off for a session, but they can't turn on one your plan doesn't include. Features like real-time TIN checks turn on automatically when your plan includes them.
FATCA and treaty questions aren't working in the prototype yet.
Your own field components
To use inputs from your own design system, list them under components in the config. For example, { country: MyCountryPicker } uses your picker for every country field on every form. You can replace one field, or every field of a type:
| Key | Replaces |
|---|---|
'account_holder.tin' | One specific field |
'country', 'tin', 'date' | Every field holding that kind of value, on every form |
'select', 'text' | Every dropdown or every text input |
Your component receives field (what to show) and form (how to read and save the value):
export const MyCountryPicker = ({ form, field }: TaxbitFieldProps) => (
<div>
<Label field={field} /> {/* Taxbit's translated label */}
<AcmeSelect
value={form.value(field.key)} {/* read */}
onChange={(v) => form.set(field.key, v)} {/* save */}
onBlur={() => form.blur(field.key)} {/* lets Taxbit time errors */}
options={field.options.map((o) => o.value)} {/* ISO codes, e.g. "US" */}
/>
<Errors form={form} field={field} /> {/* shown at the right moment */}
</div>
);Your own page layout
To control what surrounds the fields (the title, the progress bar and the buttons), pass your own page component.
const AcmePage = ({ page, back, primary, children }: TaxbitPageProps) => (
<Card>
<h1>{page.stepTitle}</h1>
<Progress value={page.index + 1} max={page.count} />
{children} {/* the fields */}
{back && <Button onClick={back.onClick}>{back.text}</Button>}
<Button primary disabled={primary.busy} onClick={primary.onClick}>{primary.text}</Button>
</Card>
);The client library still decides what the buttons do (Next or Submit, and when to move on). Your component only decides how they look.
A fully custom flow
If you need your own navigation or step order, use the useTaxbitForm hook instead of the ready-made component. Taxbit still decides the questions, and you arrange them.
const form = useTaxbitForm('W-9', { locale: 'en-US', bearerToken: token });
<TaxbitFields form={form} section={['classification', 'details']} />
<button onClick={async () => {
const outcome = await form.submit();
// 'incomplete' | 'ready_to_sign' | 'fragment' | 'ingested'
}}>Continue</button>Config options
| Option | Type | Required | Description |
|---|---|---|---|
questionnaire | string | Yes | Which form to collect. See Choosing the form. |
locale | string | Yes | The language, and the date format. Include the region (en-GB, not en), because date order depends on it. |
bearerToken | string | No | The user's short-lived token. See Authentication. |
seed | object | No | Data you already have. See Pre-fill what you know. |
prefill | 'latest' | No | Start from the user's last submission. Off by default. |
collect · skip · locked | string[] | No | Show only part of the form, or make part of it read-only. See Ask only some questions. |
fatca · treatyClaims · typesOfIncome | boolean · boolean · string[] | No | Optional question groups. |
components · page | object · component | No | Your own fields and page layout. |
onSuccess · onError · onProgress · onSubmit · onSettled | functions | No | Callbacks for each event. onProgress fires when the user moves between steps, which makes it a good place to save a draft. |
Response fields
You only need these details if you build your own components or a custom flow.
| Field | Type | Description |
|---|---|---|
complete | boolean | Whether the whole form is finished and valid. |
fields[] | Field[] | The questions to show right now, in order. Each one has a key (where to save the answer), a ui_type (text, select, radio, checkbox, date…), text.label (already translated) and errors. |
errors[] | Error[] | Every problem, each with a code (for your logic) and a text (to display). hidden: true means don't show it until the user has had a chance to answer. |
steps[] · sections[] | titled lists | How the fields group into pages, with a title for each. |
statuses[] | Status[] | Messages that aren't errors. READY_TO_SIGN means everything except the signature is done. |
submission | object | Only present when complete is true. This is the finished, signed form, and the client library submits it for you. |
Base your logic on key, code and option value. Those never change. Display the text values, but don't match on them, because the wording can change.
HTTP codes
| Code | Meaning | Fix |
|---|---|---|
200 | Taxbit checked the answers. | Read complete and errors. |
201 | The form was stored. | Save the document_id. |
400 | The request has a mistake: an unknown option, a typo or wrong capitalization in a name, or a seeded answer that conflicts with the form choice. | Read message. It names the problem. |
401 | The token is missing or has expired. | Get a new token. |
429 | Too many requests. | Wait, then retry. |
Integration checklist
- ☐ Tokens are created on your server, never in frontend code
- ☐
localecomes from the user's settings and includes a region - ☐ You use
W-FORM, or a specific form if you only ever collect one - ☐ You pre-fill data you already have
- ☐
onSuccesssaves thedocument_id - ☐
onErrorshows a retry message - ☐ Anything the user signs is visible to them (locked, not skipped)
- ☐ Your own components call
form.setandform.blur
Development status
Where the Logic API stands today. Nothing is published yet, so every item below refers to the prototype or to planned work.
Working in the prototype
- W-9, from start to finish
- Self-certification and residencies-only
- React client with your own components
- iOS client (W-9)
- Translated text
In development
- W-8 forms and DPS
- FATCA and treaty questions
- Final names and options
Planned
- How client libraries are delivered on each platform
- Android client
- How teams get access, including sandbox and production environments
How design partners help
- Tell Taxbit whether the part names (
details,residencies…) match how you want to split up collection. - Tell Taxbit which setups you need that aren't covered here.
- Integrate early against the development environment, and report anything that doesn't behave as described.
Next steps
- Logic API overview — what it does and how it compares to the React SDK
- Walkthrough — how a W-9 works under the hood
Updated 44 minutes ago

