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

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_required set to true and a tin_not_required_reason of NOT_ISSUED, NOT_REQUIRED, or OTHER (with tin_not_required_reason_other for OTHER). Without the reason, the status reports an issue.
  • signature is the name signed on the form, and it requires has_signed_and_certified to be true. For an individual you can leave it out: Taxbit derives the signature from name.
  • 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_status is COMPLETE or INCOMPLETE: whether the required information has been collected.
  • tax_documentation_status is VALID when the most recently submitted self-certification has passed validation checks, or INVALID when it has issues or missing information to correct before it can be used for reporting.
  • needs_resubmission says whether to prompt the user to submit a new self-certification.
  • issues lists what needs fixing. Each issue has an issue_type, a status (OPEN, IN_REVIEW, or RESOLVED), and details, each naming a field and 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

FieldTypeRequiredDescription
namestringOptionalThe user's legal name.
classificationstringOptionalINDIVIDUAL, or one of the entity values listed in step 2.
permanent_addressobjectOptionalfirst_line, second_line, city, state_or_province, postal_code, and country.
tax_residencesarrayOptionalOne entry per country of tax residence: country, and tin or tin_not_required with tin_not_required_reason.
date_of_birthstringOptionalISO 8601 date of birth.
city_of_birthstringOptionalCity of birth.
country_of_birthstringOptionalCountry of birth, as a two-letter code.
country_of_citizenshipstringOptionalCountry of citizenship, as a two-letter code.
controlling_personsarrayOptionalFor 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_certifiedbooleanOptionalWhether the user signed and certified the form.
signature_datestringOptionalISO 8601 timestamp of the signature.
signaturestringOptionalThe name signed. Requires has_signed_and_certified. Derived from name for an individual when left out.
signature_capacitystringOptionalFor an entity: OFFICER, EXECUTOR, or OTHER_CAPACITY.

PATCH /v1/account-owners/{id}

FieldTypeRequiredDescription
self_certification_classificationstringOptionalUsed to determine CARF and DAC8 reporting obligations. Same values as classification.
tax_residenciesarrayOptionalcountry, tin, and tin_type. Set to null to clear.
valid_self_certification_on_filebooleanOptionalWhether 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)

FieldTypeRequiredDescription
namestringRequiredThe asset's name, unique among your assets.
typestringRequiredCRYPTO, FIAT, or PRECIOUS_METAL.
codesarrayRequiredThe codes your transactions send as asset.code for this asset.
is_stablecoin_or_sempbooleanOptionalTags a stablecoin or SEMP. Creation only, CRYPTO only.

Where to go next

  • Webhooks: the ACCOUNT_OWNER_TAX_DOCUMENTATION_STATUS event carries the self_certification status 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.