Individual trader

Overview

A User is the person or institution DriveWealth authenticates, screens, and approves — it's the
identity layer, not the brokerage account itself. An Account can't be opened until its owning User
exists and clears KYC, so this is the first call in almost every onboarding integration.

The flow has three calls, in order:

StepCallResult
1POST /usersUser created — status may already be APPROVED with just base documents
2PATCH /users/USERID (zero or more times)Remaining onboarding documents attached
3GET /users/USERID/kyc-statusConfirm the granular reason code reaches a terminal value

Everything below assumes an INDIVIDUAL_TRADER — the other two userType values, CUSTODIAL and
BENEFICIARY, follow the same shape with a narrower set of documents (a beneficiary, for instance,
typically skips EMPLOYMENT_INFO entirely).

Who performs KYC depends on how your client is configured, and that changes what you should expect
from step 3:

  • Do KYC — DriveWealth performs screening. The User moves through the granular kyc.status.name
    states (KYC_NOT_READY → … → KYC_APPROVED/KYC_DENIED) as DriveWealth processes it — see
    Check KYC status for how to track that without polling.
  • No KYC / Verify KYC — your integration performs KYC/CIP yourself (typically via
    KYC_VERIFICATION_INFO) and DriveWealth trusts that result. Once the required documents are submitted,
    the User is approved immediately on the POST /users response — there's no intermediate kyc.status progression to track.

Confirm with DriveWealth which model your account is on — it explains why a sandbox call can come
back APPROVED on the first POST with no PATCH steps at all.

Create the user

One call, one required shape: a userType and an array of onboarding documents.

Four top-level fields are required — userType, username, password, and documents.

curl -X POST https://api.drivewealth.com/users \
  -H "Authorization: Bearer $DW_BEARER_TOKEN" \
  -H "dw-client-app-key: $DW_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "userType": "INDIVIDUAL_TRADER",
    "username": "jane.smith",
    "password": "my#1Account",
    "documents": [
      {
        "type": "BASIC_INFO",
        "data": {
          "firstName": "Jane",
          "lastName": "Smith",
          "country": "USA",
          "phone": "12025550149",
          "emailAddress": "[email protected]"
        }
      },
      {
        "type": "IDENTIFICATION_INFO",
        "data": {
          "value": "402724321",
          "type": "SSN",
          "citizenship": "USA"
        }
      },
      {
        "type": "PERSONAL_INFO",
        "data": {
          "birthDay": 14,
          "birthMonth": 6,
          "birthYear": 1990,
          "politicallyExposedNames": "NULL"
        }
      },
      {
        "type": "ADDRESS_INFO",
        "data": {
          "street1": "15 Exchange Place",
          "street2": "Suite 1000",
          "city": "Jersey City",
          "province": "NJ",
          "postalCode": "07302",
          "country": "USA"
        }
      }
    ]
  }'

Response — 200

{
  "id": "58b843c3-cf2c-4d6e-a27c-29ef7230b830",
  "username": "jane.smith",
  "userType": {
    "name": "INDIVIDUAL_TRADER",
    "description": "Individual Trader"
  },
  "status": {
    "name": "APPROVED",
    "description": "User approved."
  },
  "parentIB": {
    "id": "80f9b672-120d-4b73-9cc9-42fb3262c4b9",
    "name": "DriveWealth"
  },
  "documents": [
    {
      "type": "ADDRESS_INFO",
      "data": {
        "street1": "15 Exchange Place",
        "street2": "Suite 1000",
        "city": "Jersey City",
        "province": "NJ",
        "postalCode": "07302",
        "country": "USA"
      },
      "description": "Physical address information"
    },
    {
      "type": "BASIC_INFO",
      "data": {
        "firstName": "Jane",
        "lastName": "Smith",
        "displayName": "JSmith",
        "emailAddress": "[email protected]",
        "phone": "12025550149",
        "country": "USA",
        "language": "en_US"
      },
      "description": "Name, email, phone, etc."
    },
    {
      "type": "IDENTIFICATION_INFO",
      "data": {
        "value": "****4321",
        "type": "SSN",
        "citizenship": "USA",
        "description": "Social Security Number"
      },
      "description": "ID Number and citizenship"
    },
    {
      "type": "PERSONAL_INFO",
      "data": {
        "birthdate": "1990-06-14",
        "politicallyExposedNames": "NULL"
      },
      "description": "Birth date, gender, marital status, etc."
    },
    {
      "type": "TAX_INFO",
      "data": {
        "usTaxpayer": true,
        "taxTreatyWithUS": false
      },
      "description": "Tax Information"
    }
  ],
  "wlpID": "DW",
  "referralCode": "E13E1B",
  "createdWhen": "2026-08-31T19:11:24.975Z",
  "updatedWhen": "2026-08-31T19:11:24.975Z"
}

A 200 is not a guaranteed approval — but it can already be one. Submitting just the four base
documents above (no EMPLOYMENT_INFO, INVESTOR_PROFILE_INFO, COMPLIANCE_AML_INFO, or DISCLOSURES) can come back with status: APPROVED on the very first call — see who performs KYC for why. The response also grows a TAX_INFO document you never sent — DriveWealth defaults usTaxpayer / taxTreatyWithUS from citizenship. Either way, persist id immediately and treat the KYC status endpoint as the source of truth, not this response.

Document types

documents[] is a discriminated union, keyed by type. Send only what you have — add the rest later.

Each entry is { type, data }, and each type validates independently against its own required-field
list. A create call typically needs the first four rows below; the rest arrive later via PATCH as your
onboarding UI collects them, or never, if they don't apply to this user.

TypePurposeRequired data
BASIC_INFOName, country, contactfirstName, lastName, country, phone, emailAddress
IDENTIFICATION_INFOGovernment / tax IDvalue, type, citizenship
PERSONAL_INFODOB, PEP disclosurebirthDay, birthMonth, birthYear, politicallyExposedNames
ADDRESS_INFOResidential addressstreet1, city, province, postalCode, country
EMPLOYMENT_INFOEmployer & rolestatus, broker, directorOf
INVESTOR_PROFILE_INFOSuitabilityinvestmentExperience, annualIncome, networthTotal, riskTolerance, investmentObjectives, networthLiquid
COMPLIANCE_AML_INFOAML / funding sourcesfundingSources
DISCLOSURESAgreements + signaturetermsOfUse, marketDataAgreement, customerAgreement, rule14b, privacyPolicy, dataSharing, signedBy
TAX_INFOTax treaty statustaxTreatyWithUS
KYC_VERIFICATION_INFOPartner-run KYC resultverification, verificationIDType
TRUST_INFO / INSTITUTIONAL_INFO / CUSTODIAN_INFO / DIRECTOR_INFOEntity onboarding
MARGIN_DISCLOSURE / FPSL_DISCLOSUREFeature-specific disclosures

Complete onboarding

PATCH /users/USERID — attach more documents, correct a field, or sign an attestation.

The body shape is identical to create: a documents array, any subset of types. There's no separate
endpoint for the tax attestation — it's a PATCH carrying a TAX_INFO document with a nested
attestation:

{
  "documents": [
    {
      "type": "TAX_INFO",
      "data": {
        "attestation": {
          "signedBy": "cc07f91b-7ee1-4868-b8fc-823c70a1b932"
        }
      }
    }
  ]
}

And here's a corrected address, the same shape you'd use for any field-level fix:

{
  "documents": [
    {
      "type": "ADDRESS_INFO",
      "data": {
        "street1": "480 Washington Blvd",
        "city": "Jersey City",
        "province": "NJ",
        "postalCode": "07310"
      }
    }
  ]
}

Check KYC status

Two status fields, two different resolutions — know which one you're gating on.

The User object itself (from create, retrieve, or update) only ever reports a coarse status.name:
PENDING or APPROVED. That's enough to gate account creation, but not enough to tell a user why
they're stuck. For that, call the dedicated status endpoint:

curl -X GET https://api.drivewealth.com/users/58b843c3-cf2c-4d6e-a27c-29ef7230b830/kyc-status \
  -H "Authorization: Bearer $DW_BEARER_TOKEN" \
  -H "dw-client-app-key: $DW_APP_KEY"
{
  "userID": "58b843c3-cf2c-4d6e-a27c-29ef7230b830",
  "firstname": "Jane",
  "lastname": "Smith",
  "identity": {
    "number": "*******-4321",
    "dob": "1990-06-14"
  },
  "accounts": [],
  "kyc": {
    "approved": {
      "timestamp": "2026-08-31T19:11:25.020Z",
      "approvedBy": "80f9b672-120d-4b73-9cc9-42fb3262c4b9"
    },
    "accepted": {
      "acceptedBy": "SYSTEM PRINCIPAL APPROVER",
      "timestamp": "2026-08-31T19:11:25.020Z"
    }
  },
  "status": {
    "name": "APPROVED",
    "description": "User approved."
  },
  "partnerID": {
    "id": "80f9b672-120d-4b73-9cc9-42fb3262c4b9",
    "name": "Uttam_Investment"
  },
  "documents": []
}

kyc.status.name only progresses through these intermediate states for do-KYC partners (DriveWealth
performs screening). For no-KYC/verify-KYC partners, the User is approved as soon as the required documents land, so you'll typically see KYC_APPROVED right away with no prior states to track.

When it does progress, kyc.status.name is the granular reason code you'll base your next step on:

StatusMeaningNext step
KYC_NOT_READYRequired documents haven't been submitted yetPATCH the missing documents, then wait for the update event
KYC_READYDocuments complete, queued for processingWait for the update event, no action needed
KYC_PROCESSINGVerification in progressWait for the update event, no action needed
KYC_INFO_REQUIREDPII didn't match — needs a corrected PATCHSend the user back into your onboarding UI to fix the mismatched field(s), then PATCH
KYC_DOC_REQUIREDA supporting document is missingPrompt for the specific missing document, then PATCH
KYC_MANUAL_REVIEWEscalated to a human reviewerShow an "under review" state and wait — this can take longer than the automated states
KYC_APPROVEDTerminal — clearedProceed to open an Account
KYC_DENIEDTerminal — rejectedSurface the denial, don't retry the same payload

For do-KYC clients, don't poll this endpoint as your primary signal — subscribe to the kyc.created / kyc.updated events on your SQS queue. Its payload.current.status already carries the new kyc.status.name value directly, so you don't need to re-fetch kyc-status just to learn what changed:

{
  "id": "event_789ca9ba-312b-4480-9f36-3805ceb00f63",
  "type": "kyc.updated",
  "ibID": "80f9b672-120d-4b73-9cc9-42fb3262c4b9",
  "object": "KYC_UPDATED",
  "timestamp": "2026-07-23T07:08:14.121518146Z",
  "payload": {
    "current": {
      "status": "KYC_APPROVED",
      "statusMessage": "KYC Approved",
      "details": ["POOR_PHOTO_QUALITY"]
    },
    "previous": {
      "status": "KYC_APPROVED",
      "statusMessage": "KYC Approved",
      "details": ["POOR_PHOTO_QUALITY"]
    },
    "userID": "cc07f91b-7ee1-4868-b8fc-823c70a1b932"
  }
}

Compare payload.current.status against payload.previous.status to confirm it actually changed, and use payload.userID to look up the User in your own system. Reserve polling kyc-status for an occasional reconciliation pass (catching a missed or delayed event), not the main driver of your UI.

Requirements vary by country

Everything above is the USA shape. country (on BASIC_INFO / ADDRESS_INFO) and citizenship (on
IDENTIFICATION_INFO) drive country-specific rules elsewhere in the system — don't assume the US example
generalizes:

  • ID type: IDENTIFICATION_INFO.type accepts more than SSNPASSPORT, ALIEN_ID, OTHER, etc., and non-SSN types require issuingCountry (see the Document types table).
  • TAX_INFO defaults: the server auto-derives usTaxpayer / taxTreatyWithUS from citizenship — a non-US citizenship changes those defaults, and may still need a follow-up PATCH for the tax treaty
    attestation.
  • Which documents are required at all: KYC rules (which document types are mandatory, what counts as a match) are configured per country/partner and aren't fully exposed by the request schema — a payload that 200s isn't proof it satisfies every country's KYC requirements.
  • "Doc countries": some countries require an actual scanned/photographed document (ID, proof of
    address) to clear KYC, not just the structured data fields above. That upload goes through the
    separate Physical Documents API, not documents[] on this endpoint.

If you're onboarding a non-US country / citizenship, don't extend this guide's USA example by guesswork. You'll want to confirm the required document types, fields, and KYC rules for that specific region with DriveWealth and the relevant regulatory bodies first, and build your onboarding flow.


Did this page help you?