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
| Scope | Description | Risk Level |
|---|---|---|
health_id:verify | Verify and resolve patient Health IDs | Low |
patients:read | Read consented patient summary and clinical data | Medium |
consents:read | Check consent grant status | Low |
consents:write | Request patient consent | Low |
encounters:write | Push clinical encounters and recommendations | High |
labs:read | Read lab orders and results | Medium |
labs:write | Push lab results and interpretations | High |
prescriptions:read | Read prescription records | Medium |
prescriptions:write | Push prescription alerts | High |
documents:read | Read clinical documents | Medium |
documents:write | Issue official documents | High |
appointments:read | Read appointment data | Medium |
appointments:write | Create or update appointments | Medium |
pharmacy:write | Sync pharmacy stock levels | Medium |
webhooks:manage | Create and manage webhook subscriptions | Medium |
bridge_agent:sync | Bridge Agent data synchronisation | High |
emergency:access | Emergency access override (audited) | Critical |
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.
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',
}
)