Authentication

OpesCare uses three separate authentication mechanisms depending on which integration type you are using. All credentials should be kept secret and never exposed in client-side code.

Connect API — OAuth 2.0 Client Credentials (RS256 JWT)

The Connect API uses the OAuth 2.0 client_credentials grant. Your integration client has a client_id and client_secret (created in the developer portal). Exchange them for a short-lived Bearer token (1 hour TTL).

The returned access_token is a real RS256-signed JWT — three base64url segments separated by dots. It is signed with OpesCare's RSA-2048 private key and can be verified independently using OpesCare's public key.

Token Request

curl -X POST https://opescare.test/api/v1/connect/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type":    "client_credentials",
    "client_id":     "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
  }'
use Illuminate\Support\Facades\Http;

$response = Http::post('https://opescare.test/api/v1/connect/auth/token', [
    'grant_type'    => 'client_credentials',
    'client_id'     => env('OPESCARE_CLIENT_ID'),
    'client_secret' => env('OPESCARE_CLIENT_SECRET'),
]);

$accessToken = $response->json('access_token');
$expiresIn   = $response->json('expires_in'); // 3600 seconds
async function getToken() {
  const res = await fetch('https://opescare.test/api/v1/connect/auth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type:    'client_credentials',
      client_id:     process.env.OPESCARE_CLIENT_ID,
      client_secret: process.env.OPESCARE_CLIENT_SECRET,
    }),
  });
  const { access_token, expires_in } = await res.json();
  return access_token; // RS256 JWT — cache until expires_in seconds
}
import requests, os

def get_token() -> str:
    resp = requests.post('https://opescare.test/api/v1/connect/auth/token', json={
        'grant_type':    'client_credentials',
        'client_id':     os.environ['OPESCARE_CLIENT_ID'],
        'client_secret': os.environ['OPESCARE_CLIENT_SECRET'],
    })
    resp.raise_for_status()
    return resp.json()['access_token']  # RS256 JWT — cache for 3600s
import java.net.http.*;
import java.net.URI;

String body = String.format(
    "{\"grant_type\":\"client_credentials\",\"client_id\":\"%s\",\"client_secret\":\"%s\"}",
    System.getenv("OPESCARE_CLIENT_ID"),
    System.getenv("OPESCARE_CLIENT_SECRET")
);
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://opescare.test/api/v1/connect/auth/token"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());

Token Response

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJvcGVzY2FyZS1jb25uZWN0...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "health_id:verify patients:read consents:read consents:write"
}

Using the Token

curl https://opescare.test/api/fhir/R4/metadata \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

Available Scopes

ScopeDescriptionRisk Level
health_id:verifyVerify and resolve patient Health IDsLow
patients:readRead consented patient summary and clinical dataMedium
consents:readCheck consent grant statusLow
consents:writeRequest patient consentLow
encounters:writePush clinical encounters and recommendationsHigh
labs:readRead lab orders and resultsMedium
labs:writePush lab results and interpretationsHigh
prescriptions:readRead prescription recordsMedium
prescriptions:writePush prescription alertsHigh
documents:readRead clinical documentsMedium
documents:writeIssue official documentsHigh
appointments:readRead appointment dataMedium
appointments:writeCreate or update appointmentsMedium
pharmacy:writeSync pharmacy stock levelsMedium
webhooks:manageCreate and manage webhook subscriptionsMedium
bridge_agent:syncBridge Agent data synchronisationHigh
emergency:accessEmergency access override (audited)Critical
Tokens expire after 3600 seconds (1 hour). Cache them and re-request when they expire. Never request a new token for every API call. The OpesCare SDKs handle token caching and refresh automatically.

SDK — OAuth2 Client Credentials via SDK

When using the official OpesCare SDK, authentication is handled automatically. Pass your client_id and client_secret to the SDK client at construction time. The SDK fetches and caches the Bearer token, and refreshes it 60 seconds before expiry.

Never include client credentials in client-side JavaScript or mobile apps. Use server-side code only.
use OpesCare\OpesCareClient;

// Token is fetched automatically at construction — raises AuthenticationError on bad credentials
$client = new OpesCareClient(
    clientId:     env('OPESCARE_CLIENT_ID'),
    clientSecret: env('OPESCARE_CLIENT_SECRET'),
    environment:  'sandbox', // or 'production'
);
import { OpesCareClient } from '@opescare/sdk';

// Token is fetched at construction — throws AuthenticationError on bad credentials
const client = await OpesCareClient.create({
  clientId:     process.env.OPESCARE_CLIENT_ID,
  clientSecret: process.env.OPESCARE_CLIENT_SECRET,
  environment:  'sandbox', // or 'production'
});
from opescare import OpesCareClient

# Token is fetched at construction — raises AuthenticationError on bad credentials
client = OpesCareClient(
    client_id=os.environ['OPESCARE_CLIENT_ID'],
    client_secret=os.environ['OPESCARE_CLIENT_SECRET'],
    environment='sandbox',  # or 'production'
)

Bridge Agent — X-Bridge-Agent-Key

Bridge Agents authenticate using two headers: X-Bridge-Agent-ID (your agent's ID) and X-Bridge-Agent-Key (the agent key from the developer portal). The key is verified server-side against its SHA-256 hash stored in the database.

If you are using the official Bridge Agent daemon (pip install opescare-bridge-agent), authentication is handled automatically. The headers below are for custom integrations only.

curl -X POST https://opescare.test/api/v1/bridge/heartbeat \
  -H "X-Bridge-Agent-ID: YOUR_AGENT_ID" \
  -H "X-Bridge-Agent-Key: YOUR_AGENT_KEY" \
  -H "X-Bridge-Timestamp: 1717228800" \
  -H "Content-Type: application/json" \
  -d '{"agent_id":"YOUR_AGENT_ID","facility_id":"YOUR_FACILITY_ID","version":"1.0.0"}'
Http::withHeaders([
    'X-Bridge-Agent-ID'  => env('BRIDGE_AGENT_ID'),
    'X-Bridge-Agent-Key' => env('BRIDGE_AGENT_KEY'),
    'X-Bridge-Timestamp' => time(),
])->post('https://opescare.test/api/v1/bridge/heartbeat', [
    'agent_id'    => env('BRIDGE_AGENT_ID'),
    'facility_id' => env('BRIDGE_FACILITY_ID'),
    'version'     => '1.0.0',
]);
import requests, os, time

requests.post('https://opescare.test/api/v1/bridge/heartbeat',
    headers={
        'X-Bridge-Agent-ID':  os.environ['BRIDGE_AGENT_ID'],
        'X-Bridge-Agent-Key': os.environ['BRIDGE_AGENT_KEY'],
        'X-Bridge-Timestamp': str(int(time.time())),
    },
    json={
        'agent_id':    os.environ['BRIDGE_AGENT_ID'],
        'facility_id': os.environ['BRIDGE_FACILITY_ID'],
        'version':     '1.0.0',
    }
)