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:
| Parameter | Required | Description |
|---|---|---|
grant_type | yes | Must be client_credentials. |
client_id | yes | Your integration's client id. |
client_secret | yes | The matching secret. Keep it server-side. |
scope | no | Space-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/jsonAuth errors
The token endpoint follows the RFC 6749 §5.2 error shape:
| Status | error | Cause |
|---|---|---|
| 400 | unsupported_grant_type | grant_type wasn't client_credentials. |
| 400 | invalid_request | Missing client_id or client_secret. |
| 400 | invalid_scope | A requested scope exceeds your client's grant. |
| 401 | invalid_client | Credential check failed. The response never hints which check failed. |
Keep secrets server-side
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.