Skip to content

Authentication

The MedFlo integration API uses the OAuth2 client-credentials grant (RFC 6749 §4.4). You exchange a client_id and client_secret for a short-lived, scoped bearer token, then send that token on every request.

Callers are integration principals — a machine credential issued per integration, not a human user session. The credential is bound to an organization, a set of scopes, and a set of facilities; the token you receive inherits exactly those.

The token endpoint

POST/api/v1/oauth/token

This is the only unauthenticated write endpoint, and it is rate-limited by IP to blunt credential stuffing. Send the body as application/x-www-form-urlencoded:

ParameterRequiredDescription
grant_typeyesMust be client_credentials.
client_idyesYour integration's client id.
client_secretyesThe matching secret. Keep it server-side.
scopenoSpace-delimited scopes to request. Omit to receive all scopes your client holds. Requesting a scope you weren't granted returns invalid_scope (400).

Request a token

# Exchange YOUR issued client credentials for a short-lived bearer token.
# Set these from the credentials issued to you after your access request is approved.
CLIENT_ID="your-client-id"
CLIENT_SECRET="your-client-secret"

curl -s -X POST 'https://medflo-pcc-vendor-api-eez5kqwsxa-uw.a.run.app/api/v1/oauth/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  --data-urlencode 'scope=residents.read coverage.read clinical.read'

# → { "access_token": "eyJ...", "token_type": "Bearer",
#     "expires_in": 3600, "scope": "residents.read coverage.read clinical.read" }

The token response

The response is the standard OAuth2 token body (not the {data, meta, error} envelope), so any off-the-shelf OAuth client works:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "residents.read coverage.read clinical.read"
}

Tokens are short-lived (expires_in seconds — one hour). Re-fetch when a token expires; there is no refresh token in the client-credentials grant — just request a new one.

Use the token

Send it as a bearer token on every resource request:

GET /api/v1/ofctx/{org_id}/{facility_id}/residents
Host: medflo-pcc-vendor-api-eez5kqwsxa-uw.a.run.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/json

Auth errors

The token endpoint follows the RFC 6749 §5.2 error shape:

StatuserrorCause
400unsupported_grant_typegrant_type wasn't client_credentials.
400invalid_requestMissing client_id or client_secret.
400invalid_scopeA requested scope exceeds your client's grant.
401invalid_clientCredential check failed. The response never hints which check failed.

Keep secrets server-side

Treat client_secret like a password — store it in a secret manager and exchange it for a token from your backend. There are no shared or public credentials: each integration is issued its own private credentials after an admin approves your access request.