Skip to main content

Quickstart

A complete carrier connection, start to finish, in curl. Every call here is one your backend would make. Nothing in this walkthrough requires a browser except where noted.

Set up your environment first:

export TPA_BASE=https://app.tpastream.com/connect/v1
export TPA_CONNECT_TOKEN=... # your public Connect token
export TPA_MEMBER=member@example.com

And a helper so the examples stay readable:

tpa() { curl -sS -H "X-TPAStream-Token: $TPA_CONNECT_TOKEN" \
-H "X-Connect-API-Version: 1" \
-H "Content-Type: application/json" "$@"; }

1. Bootstrap the member​

This is the only call that may reference a member who doesn't exist yet. It creates or resolves the member, attaches them to the employer, and returns the carriers available to them.

tpa -X POST "$TPA_BASE/bootstrap" -d '{
"system_key": "EMP-1234",
"vendor": "internal",
"employer_name": "Acme Corp",
"user_first_name": "Dana",
"user_last_name": "Reyes",
"user_email": "member@example.com",
"date_of_birth": "1985-04-12"
}'
{
"data": {
"user": { "id": 90210, "email": "member@example.com" },
"employer": { "id": 203246, "name": "Acme Corp" },
"tenant": { "id": 1141, "name": "Acme Benefits" },
"payers": [
{ "id": 6, "name": "UnitedHealthcare", "logo_url": "https://..." },
{ "id": 116, "name": "Blue Cross Blue Shield of Massachusetts" }
]
}
}

Hold onto employer.id and user.id. You will need the employer id on almost every subsequent call.

Render payers however you like — this is your carrier picker.

2. Fetch the carrier's credential form​

Carriers ask for different things. Some want a username and password, some add a member id, a date of birth, or security questions. Rather than hard-coding per-carrier forms, fetch the schema:

tpa -G "$TPA_BASE/payer/203246/6" --data-urlencode "email=$TPA_MEMBER"
{
"data": {
"id": 6,
"name": "UnitedHealthcare",
"logo_url": "https://...",
"onboard_form": { "...": "JSON Schema describing the fields" },
"onboard_ui_schema": { "...": "presentation hints for those fields" },
"has_security_questions": false,
"supports_interoperability_apis": false,
"register_url": "https://www.myuhc.com/register",
"website_home_url_netloc": "myuhc.com"
}
}

onboard_form is a JSON Schema. Drive your form from it and you get new carriers, and changes to existing ones, without a deploy.

Two fields worth branching on:

  • supports_interoperability_apis: true means this carrier uses an OAuth redirect instead of credentials. Skip to Patient Access API — the rest of this walkthrough does not apply.
  • register_url is where a member with no carrier account should be sent to make one. Surface it near your form.

3. Fetch the terms of use​

The member must accept before you can submit credentials. Fetch the current text rather than reproducing it:

tpa -G "$TPA_BASE/terms_of_service" --data-urlencode "email=$TPA_MEMBER"
{ "data": { "html_string": "<div>...</div>" } }

Render it, and collect two separate affirmative acknowledgements: the terms of use themselves, and consent for the tenant to access the account on the member's behalf. They map to accept and tenants_accept in the next step.

4. Submit credentials​

tpa -X POST "$TPA_BASE/policy_holder" -d '{
"user_email": "member@example.com",
"employer_id": 203246,
"payer_id": 6,
"username": "dreyes",
"password": "hunter2",
"date_of_birth": "1985-04-12",
"accept": true,
"tenants_accept": [1141]
}'

Any additional fields the carrier's onboard_form declares go at the top level of this same object.

{
"data": {
"policy_holder_id": 630364,
"task_id": "3bb088ed-cc38-4e0f-919f-f2060014ac44",
"task_token": "eyJhbGciOiJIUzI1NiIs..."
}
}

Three things to note:

  • accept must be truthy and tenants_accept must be a non-empty array of tenant ids. A string will be rejected. Missing either returns 403.
  • task_id identifies the validation now running against the carrier. This is what you watch.
  • task_token authenticates the event stream subscription for this validation. It is short-lived by design; a fresh one is minted every time you GET the policy holder while the validation is running.

To reconnect an existing policy holder after a credential change, send the same body as a PUT to /policy_holder/{policy_holder_id}.

5. Watch the validation​

The credential submit returned a task_id and a task_token. Subscribe to the event stream and states arrive as they happen:

curl -N "https://app.tpastream.com/v3/connect/progress/3bb088ed-cc38-4e0f-919f-f2060014ac44/stream?token=$TASK_TOKEN"

Each connection is capped at ~10 minutes; on the timeout event, re-fetch the policy holder for a fresh task_token and resubscribe — the MFA page has the loop. Prefer not to hold streams open? Polling works too:

tpa -G "$TPA_BASE/validate-credentials/630364/3bb088ed-cc38-4e0f-919f-f2060014ac44" \
--data-urlencode "email=$TPA_MEMBER"
{ "data": { "id": "3bb088ed-...", "state": "PENDING" } }

Either way, you will land on one of:

stateWhat it meansWhat to do
PENDINGStill working.Keep watching.
WAITING_FOR_METHOD_CHOICEThe carrier wants a one-time code and is offering delivery methods.See multi-factor.
WAITING_FOR_TWO_FACTOR_CODEA code has been sent.See multi-factor.
SUCCESSValidation finished.Read credentials_are_valid.
FAILUREValidation could not complete.Read message and surface it.

A terminal success:

{
"data": {
"id": "3bb088ed-...",
"state": "SUCCESS",
"credentials_are_valid": true,
"pending": false
}
}

credentials_are_valid: true means the carrier accepted the login. pending: true means we have not yet reached a verdict and will keep trying in the background — treat it as "accepted for now, check back", not as failure.

Most connections that need a one-time code pass through here. Read Multi-factor authentication before you ship; about a third of real connections hit it.

6. Confirm the connection​

tpa -G "$TPA_BASE/policy_holder/630364" \
--data-urlencode "employer_id=203246" \
--data-urlencode "email=$TPA_MEMBER"
{
"data": {
"policy_holder_id": 630364,
"uuid": "8d5f...",
"payer_id": 6,
"login_problem": null,
"login_needs_correction": false,
"last_successful_crawl_end": null,
"claims_synced_count": 0
}
}

login_problem: null with login_needs_correction: false is a healthy connection. Claims will not be there yet — the first crawl runs asynchronously and can take a while. Subscribe to the First Crawl Completion Webhook rather than polling for them.

7. Show the member their connections​

For a "manage my carriers" screen:

tpa -G "$TPA_BASE/fix-credentials" --data-urlencode "email=$TPA_MEMBER" \
-H "X-Connect-Access-Token: $TPA_ACCESS_TOKEN"

Returns every policy holder for the member with its current status, so you can render reconnect prompts for the ones that need attention. This endpoint requires a connect access token.

Where to go next​