Self-certification & transaction reporting
Collect a self-certification from each user, check that it's valid, tag your stablecoins, and send the activity CARF and DAC8 reporting draws on.
Part of Digital Asset Reporting. By the end of this page a user has a valid self-certification on file, your stablecoins are tagged, and the user's activity is flowing to Taxbit.
The self-certification can come from either of two routes: the React SDK, where the user fills in the SELF-CERT questionnaire in your app, or the API, where you send a self-certification you hold from your backend. Digital platforms reporting seller income use a different questionnaire, the DPS; see Collect seller data.
Before you begin
- A bearer token. See Authenticate your tenant.
- The user's account owner and account. See Create account owners and accounts.
1. Set up the account owner
The account owner is the user. Its account_owner_type is INDIVIDUAL or ENTITY. Set its self_certification_classification, which is used to determine CARF and DAC8 reporting obligations, and its tax_residencies, one entry per country the user is resident in for tax:
curl -X PATCH $BASE_URL/v1/account-owners/usr_4d8a2e61 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"self_certification_classification": "INDIVIDUAL",
"tax_residencies": [
{ "country": "IT", "tin": "RNLSFO91H42F205X", "tin_type": "OTHER" }
]
}'A 200 returns the updated account owner in data. self_certification_classification takes the same values as classification on the self-certification in step 2.
If you collected the user's self-certification outside Taxbit and won't send it, set valid_self_certification_on_file to say whether a valid one is on file. Once a self-certification is on file in Taxbit, the field reflects that document's validation status instead of the value you set.
The other account owner fields are on Create account owners and accounts and Collect seller data.
2. Collect the self-certification
In your app, with the SDK
Render the questionnaire with questionnaire="SELF-CERT". Set data.accountHolder.isIndividual to true for an individual or false for an entity; an entity also needs selfCertificationAccountType (FINANCIAL_INSTITUTION, ACTIVE_NON_FINANCIAL_ENTITY, or PASSIVE_NON_FINANCIAL_ENTITY). The Questionnaire integration guide covers installing the SDK and rendering the questionnaire, entity flows included.
From your backend, with the API
Send the self-certification you hold. Here the user is an individual resident in Italy:
curl -X POST $BASE_URL/v1/account-owners/usr_4d8a2e61/tax-documentation-data/self-certification \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Sofia Rinaldi",
"classification": "INDIVIDUAL",
"permanent_address": {
"first_line": "Via dei Glicini 18",
"city": "Milan",
"postal_code": "20144",
"country": "IT"
},
"tax_residences": [
{ "country": "IT", "tin": "RNLSFO91H42F205X" }
],
"date_of_birth": "1991-06-02",
"city_of_birth": "Milan",
"country_of_birth": "IT",
"country_of_citizenship": "IT",
"has_signed_and_certified": true,
"signature_date": "2026-10-07T00:00:00.000Z"
}'A 201 returns the stored submission in data, with TINs masked. On the self-certification the residence list is tax_residences, without a tin_type; on the account owner it is tax_residencies.
The endpoint stores what you send and doesn't reject incomplete or inconsistent data. Taxbit validates the submission and reports problems through the status in step 3, so send the complete form, then read the status.
A few rules for the fields:
- For a country where the user has no TIN, send the entry with
tin_not_requiredset totrueand atin_not_required_reasonofNOT_ISSUED,NOT_REQUIRED, orOTHER(withtin_not_required_reason_otherforOTHER). Without the reason, the status reports an issue. signatureis the name signed on the form, and it requireshas_signed_and_certifiedto betrue. For an individual you can leave it out: Taxbit derives the signature fromname.- Country fields take a two-letter ISO 3166-1 alpha-2 code, such as
IT.
Entities. For an entity, classification is one of FINANCIAL_INSTITUTION_DEPOSITORY_INSTITUTION, FINANCIAL_INSTITUTION_CUSTODIAL_INSTITUTION, FINANCIAL_INSTITUTION_INSURANCE_COMPANY, FINANCIAL_INSTITUTION_NON_REPORTING, FINANCIAL_INSTITUTION_INVESTMENT_ENTITY_MANAGED, FINANCIAL_INSTITUTION_INVESTMENT_ENTITY_OTHER, ACTIVE_NFE_GOVERNMENT_ENTITY, ACTIVE_NFE_CENTRAL_BANK, ACTIVE_NFE_INTERNATIONAL_ORGANIZATION, ACTIVE_NFE_PUBLIC_CORPORATION, ACTIVE_NFE_OTHER, or PASSIVE_NFE. A PASSIVE_NFE self-certification must include at least one entry in controlling_persons, each with the person's name, role, and their own address, birth, and tax_residences details; without one, the status reports that "at least one controlling person must be provided for classification PASSIVE_NFE". The person who signs for the entity goes in signature, and their capacity in signature_capacity (OFFICER, EXECUTOR, or OTHER_CAPACITY, with signature_capacity_other for the last). The SDK's SELF-CERT questionnaire walks entities through all of this in your app.
3. Check the status
Read the account owner's tax documentation status:
curl $BASE_URL/v1/account-owners/usr_4d8a2e61/tax-documentation-status \
-H "Authorization: Bearer $TOKEN"For the submission above:
{
"self_certification": {
"data_collection_status": "COMPLETE",
"tax_documentation_status": "VALID",
"needs_resubmission": false,
"issues": []
}
}self_certification reports on the latest self-certification:
data_collection_statusisCOMPLETEorINCOMPLETE: whether the required information has been collected.tax_documentation_statusisVALIDwhen the most recently submitted self-certification has passed validation checks, orINVALIDwhen it has issues or missing information to correct before it can be used for reporting.needs_resubmissionsays whether to prompt the user to submit a new self-certification.issueslists what needs fixing. Each issue has anissue_type, astatus(OPEN,IN_REVIEW, orRESOLVED), anddetails, each naming afieldand describing the problem.
A submission that left out tin_not_required_reason, for example, reads:
{
"self_certification": {
"data_collection_status": "INCOMPLETE",
"tax_documentation_status": "INVALID",
"needs_resubmission": true,
"issues": [
{
"issue_type": "INCOMPLETE_DATA",
"status": "OPEN",
"created_at": "2026-10-07T20:11:52.741Z",
"details": [
{
"field": "tax_residences.0.tin_not_required_reason",
"description": "TIN-not-required reason must be provided when TIN is not required"
}
]
},
{
"issue_type": "INCONSISTENT_DATA",
"status": "OPEN",
"created_at": "2026-10-07T20:11:52.741Z",
"details": [
{
"field": "tax_residences.0.tin_not_required_reason",
"description": "must be provided"
}
]
}
]
}
}The issue types that apply to self-certifications are INCOMPLETE_DATA, INCONSISTENT_DATA, INCOMPLETE_ADDRESS, US_INDICIA, CHANGE_IN_CIRCUMSTANCES, CBI_RBI_CONFIRMATION, and INCOMPLETE_GIIN; the SDK component reference describes each.
Fixing issues. Correct the fields the issues name and submit the complete form again. The status reports on your latest submission: once it passes, the issues are gone and needs_resubmission is false.
On the account owner, valid_self_certification_on_file is the one-field summary. It is absent until a self-certification exists, then reflects the latest one's validation status, and is true once the status is VALID.
The stored data. GET /v1/account-owners/{id}/tax-documentation-data returns the stored self-certification under selfCertification, with document_type SELF_CERTIFICATION, is_individual, and, for an entity, self_certification_account_type. TINs are masked unless you pass unmask=true.
4. Tag stablecoins and send the activity
Assets. Configure the crypto assets your platform supports with POST /v1/assets, up to 500 per request. Tag a stablecoin or Specified Electronic Money Product (SEMP) with is_stablecoin_or_semp when you create it: the tag can only be set on creation, only on a CRYPTO asset, and a later PATCH can't change it.
curl -X POST $BASE_URL/v1/assets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '[
{ "name": "USD Coin", "type": "CRYPTO", "codes": ["USDC"], "is_stablecoin_or_semp": true },
{ "name": "Solana", "type": "CRYPTO", "codes": ["SOL"] }
]'Each element is processed on its own and comes back, in order, with a status of created or error, so one failure doesn't block the rest. A tagged asset comes back with stablecoin_reporting_jurisdictions, which Taxbit derives from a maintained list of codes: US marks a US (IRS) stablecoin, and any other code an OECD (CARF or CRS) stablecoin reportable in that country. Its qualification_status is QUALIFIED or NOT_QUALIFIED for the current calendar year, and a newly created stablecoin reports QUALIFIED. qualification_status is omitted for an asset that isn't a stablecoin. The Configure Assets reference has the full schema.
Transactions. Send each user's activity with POST /v1/transactions/external-id, which creates a transaction or updates one you sent before. Set up inventory and gains shows the shapes for buys, sells, trades, and transfers, and the Transaction Data Model has every field. Transaction data can also arrive as CSV files; the template is available on request.
5. Find what Taxbit produces
When Taxbit generates a CARF or CRS document for an account, it appears in GET /v1/accounts/{id}/tax-documents with that type. Retrieve US tax forms shows how that endpoint works, including its expiring download URLs.
If the request fails
These apply to the self-certification POST.
- 400. The request is malformed. Incomplete or inconsistent data doesn't return a 400; it shows up as issues in the status.
- 401. The bearer token is missing, invalid, or expired. Mint a new one and retry.
- 404. No account owner has that
id. Create it first, as Create account owners and accounts shows. - 429. Too many requests. Slow down and retry.
Parameters
Only the fields used above. The full schemas are in the Self-certification reference, the Account Owners reference, and the Configure Assets reference.
POST /v1/account-owners/{id}/tax-documentation-data/self-certification
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Optional | The user's legal name. |
classification | string | Optional | INDIVIDUAL, or one of the entity values listed in step 2. |
permanent_address | object | Optional | first_line, second_line, city, state_or_province, postal_code, and country. |
tax_residences | array | Optional | One entry per country of tax residence: country, and tin or tin_not_required with tin_not_required_reason. |
date_of_birth | string | Optional | ISO 8601 date of birth. |
city_of_birth | string | Optional | City of birth. |
country_of_birth | string | Optional | Country of birth, as a two-letter code. |
country_of_citizenship | string | Optional | Country of citizenship, as a two-letter code. |
controlling_persons | array | Optional | For an entity: name, role, ownership_percentage, date_of_birth, city_of_birth, country_of_birth, country_of_citizenship, address, and tax_residences. Required in practice for PASSIVE_NFE. |
has_signed_and_certified | boolean | Optional | Whether the user signed and certified the form. |
signature_date | string | Optional | ISO 8601 timestamp of the signature. |
signature | string | Optional | The name signed. Requires has_signed_and_certified. Derived from name for an individual when left out. |
signature_capacity | string | Optional | For an entity: OFFICER, EXECUTOR, or OTHER_CAPACITY. |
PATCH /v1/account-owners/{id}
| Field | Type | Required | Description |
|---|---|---|---|
self_certification_classification | string | Optional | Used to determine CARF and DAC8 reporting obligations. Same values as classification. |
tax_residencies | array | Optional | country, tin, and tin_type. Set to null to clear. |
valid_self_certification_on_file | boolean | Optional | Whether a valid self-certification is on file. Replaced by the latest self-certification's validation status once one is on file in Taxbit. |
POST /v1/assets (an array of up to 500)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Required | The asset's name, unique among your assets. |
type | string | Required | CRYPTO, FIAT, or PRECIOUS_METAL. |
codes | array | Required | The codes your transactions send as asset.code for this asset. |
is_stablecoin_or_semp | boolean | Optional | Tags a stablecoin or SEMP. Creation only, CRYPTO only. |
Where to go next
- Webhooks: the
ACCOUNT_OWNER_TAX_DOCUMENTATION_STATUSevent carries theself_certificationstatus whenever it changes, so you don't have to poll. - The Questionnaire integration guide for collecting self-certifications in your app.
- Collect seller data for the DPS route digital platforms use.
Updated about 1 hour ago

