Skip to Content
API access is available to organizations with a signed BAA. Request API access →
Connect with OAuth

Connect with OAuth

OAuth lets an org admin connect your app to their BloomText account. Your app then acts as that admin: it reads and searches their conversations, sends in the ones they’re part of, and makes the same patient calls as an API key.

For now, only org admins can connect apps. Anyone else is sent back to your redirect URI with error=access_denied.

Step through the flow to see every request, who makes it, and how long each token lasts.

Before you start

Only apps registered with BloomText can connect. Request API access to register yours.

We register your app

After your organization signs a BAA, we register your app and send you its client ID and client secret. Tell us the name users should see and every redirect URI your app uses. Redirect URIs must match exactly and use HTTPS; http://localhost URIs are allowed for development.

An admin approves your app

A BloomText admin in each organization approves your app before anyone there can connect it. Until then, people in that organization skip the approval screen and go straight back to your redirect URI with error=access_denied.

Admins connect one at a time

Each org admin signs in and approves your app themselves, and your app keeps a separate token for each.

Keep the client secret on your server. Never put it in a browser, a mobile app, a public repository, or an AI prompt.

The authorization flow

BloomText uses the authorization code flow with PKCE. PKCE is required for every app, including server-side apps with a client secret. Click through the flow, or read the steps below.

Org adminin their browser
Your appyour server
app.bloomtext.comsign-in and tokens
api.bloomtext.comthe API
Step 1 of 10 · Before

An org admin approves your app for the organization

Org admin → app.bloomtext.com

Once per organization, in BloomText’s admin settings. Until an admin approves it, anyone who tries to connect is sent straight back to your app with error=access_denied.

We register your app first, after a BAA, and send its client ID and client secret.

How long a connection lasts
Connect12 months: approve again
  • Access token 1 hour. Refresh when it expires.
  • Refresh token new one on every refresh; ends after 90 days unused.
  • Connection 12 months at most, or until revoked.

Send the user to BloomText

Create a random state and a PKCE code_verifier, store both with the user’s session, and redirect the user to the authorize URL.

Authorize URL
https://app.bloomtext.com/oauth/authorize ?response_type=code &client_id=bt_client_7Hq2mX9aLk &redirect_uri=https://ehr.example.com/bloomtext/callback &scope=messages.read offline_access &state=af0ifjsldkj &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256
response_typestringRequired

Always code.

client_idstringRequired

Your app’s client ID.

redirect_uriURLRequired

Where BloomText sends the user back. Must exactly match a redirect URI registered for your app.

scopestringRequired

Space-separated scopes. Include offline_access to get a refresh token.

statestringRequired

A random value you check on the way back, to protect against cross-site request forgery.

code_challengestringRequired

The base64url-encoded SHA-256 hash of your code_verifier.

code_challenge_methodstringRequired

Always S256.

The user signs in and approves

BloomText hosts the sign-in and approval pages, so users sign in with their usual password and multi-factor authentication. A user who is already signed in sees only the approval screen, which names your app, the user’s organization, and what each requested scope allows.

Handle the redirect

BloomText sends the user back to your redirect URI:

Redirect
https://ehr.example.com/bloomtext/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj

Check that state matches what you stored, then exchange the code. Codes expire after 10 minutes and work once.

If the user declines, isn’t an org admin, or their organization hasn’t approved your app, the redirect carries error=access_denied and an error_description instead of a code. The one exception is a bad client_id or an unregistered redirect_uri: as OAuth requires, BloomText shows an error page instead of redirecting.

Exchange the code for tokens

Call the token endpoint from your server, authenticating with your client ID and secret.

Request
curl https://app.bloomtext.com/oauth/token \ -u "$BLOOMTEXT_CLIENT_ID:$BLOOMTEXT_CLIENT_SECRET" \ -d grant_type=authorization_code \ -d code=SplxlOBeZQQYbYS6WxSbIA \ -d redirect_uri=https://ehr.example.com/bloomtext/callback \ -d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Response · 200 OK
{ "access_token": "bt_at_Vq8yG2mN0cT4…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "bt_rt_k3Jd9Qp1Xw7s…", "scope": "messages.read offline_access" }

Store the refresh token against the user, encrypted, the same way you store PHI.

Call the API

Send the access token as a Bearer token. Retrieve the profile to confirm which user connected.

Request
curl https://api.bloomtext.com/v1/me \ -H "Authorization: Bearer $BLOOMTEXT_ACCESS_TOKEN"
Response · 200 OK
{ "user": { "id": "8b9c2d2f-6b1a-44f4-a7b1-0d6d3f2d6f55", "name": "Alex Example", "role": "admin" }, "organization": { "id": "6db1e3f5-9b7f-4f2b-8be1-0f1e1d7d7d8c", "name": "Example Clinic" }, "app": { "client_id": "bt_client_7Hq2mX9aLk", "name": "Acme EHR Assistant" }, "scopes": ["messages.read", "offline_access"], "expires_at": "2027-09-21T15:04:05Z" }

What a token can reach

An OAuth token acts as the org admin who connected it. It reaches:

  • the admin’s own conversations, whether they were added directly or through a team, including messages sent before they joined, but no one else’s staff conversations;
  • patients, always named by patient ID, phone number, or MRN, with all of that patient’s conversations in the organization, like an API key;
  • the clinic phone numbers of the teams the admin is on.

Anything else returns 404, the same as something that doesn’t exist. Access lasts only while the admin has it: removing them from a conversation, or taking away their admin role, cuts off the token on the next request.

Messages your app sends appear under the admin’s name. Staff also see which app sent them; patients see only the admin. See Core concepts.

Tokens and how long they last

TokenLifetimeNotes
Authorization code10 minutesWorks once.
Access token1 hourSend it with every request. expires_in says how many seconds it has left.
Refresh tokenRotates on every useIssued only with offline_access. Stops working after 90 days unused.
The connection12 monthsAfter 12 months the user must approve your app again, even if you use it every day. expires_at on the profile says when.

The user connects once and your app keeps working for up to a year: refresh the access token when it expires, and ask the user to reconnect when expires_at is near.

Refresh an access token

Request
curl https://app.bloomtext.com/oauth/token \ -u "$BLOOMTEXT_CLIENT_ID:$BLOOMTEXT_CLIENT_SECRET" \ -d grant_type=refresh_token \ -d refresh_token=bt_rt_k3Jd9Qp1Xw7s…

Every refresh returns a new refresh token and invalidates the old one. Save the new one before you do anything else.

If a refresh request times out and you don’t know whether it worked, retry with the same refresh token within 30 seconds. BloomText returns the tokens it issued the first time.

Using an old refresh token after that 30-second window revokes the whole connection, because it usually means the token was copied. If two workers can refresh the same user’s token, make them share a lock.

When a connection ends

A connection stops working when:

  • the user disconnects your app from Settings → Connected apps in BloomText;
  • an admin revokes your app for their organization;
  • the admin is deactivated, loses their admin role, leaves the organization, or resets their password;
  • your app goes 90 days without refreshing;
  • 12 months pass since the user approved your app.

After that, API calls return 401 with code: invalid_token, and refreshing returns 400 with error: invalid_grant. Show the user a “Reconnect BloomText” button that starts the flow again.

To disconnect a user from your side, for example when they turn off the integration, revoke the refresh token:

Request
curl https://app.bloomtext.com/oauth/revoke \ -u "$BLOOMTEXT_CLIENT_ID:$BLOOMTEXT_CLIENT_SECRET" \ -d token=bt_rt_k3Jd9Qp1Xw7s…

Token endpoint errors

The token endpoint returns standard OAuth errors as JSON: {"error": "invalid_grant", "error_description": "…"}.

ErrorStatusWhat to do
invalid_grant400The code or refresh token is expired, used, or revoked, or the code_verifier or redirect_uri doesn’t match the authorization request. For an expired or revoked token, send the user through the authorize URL again.
invalid_client401Check your client ID and secret.
invalid_request400A required parameter is missing, repeated, or malformed.
invalid_scope400A refresh asked for a scope the user didn’t grant. Refresh with the same or fewer scopes.
unauthorized_client400Your app isn’t allowed to use this grant type. Contact us.
unsupported_grant_type400Use authorization_code or refresh_token.

API endpoints use problem details instead. 401 and 403 responses also carry a WWW-Authenticate header, like Bearer error="invalid_token", resource_metadata="https://api.bloomtext.com/.well-known/oauth-protected-resource/v1".

Server metadata

OAuth libraries can configure themselves from these discovery documents.

DocumentURL
Authorization server metadata (RFC 8414 )https://app.bloomtext.com/.well-known/oauth-authorization-server
Protected resource metadata (RFC 9728 ) for https://api.bloomtext.com/v1https://api.bloomtext.com/.well-known/oauth-protected-resource/v1
Last updated on