QUBS Integrations API (v1)
Partner-facing API providing read access to selected QUBS configuration and scheduling data, plus reporting submission endpoints for tele reporting workflows.
Authentication is via a bearer JWT in the Authorization header. Authorization is domain-scoped; all requests include a {domain} path parameter. Tokens are issued via the OAuth 2.0 client credentials flow (see Authentication below).
Machine-to-machine token handling:
- Access tokens expire after 60 minutes.
- Cache and reuse the same access token for API calls until it is close to expiry; do not request a new token for every API request.
- Request a replacement token around 50-55 minutes after issue, or after an authorization failure that indicates the token has expired.
- The client credentials flow does not use refresh tokens. Token responses contain an
access_token,token_type, andexpires_in.
Scopes/audience: no additional scopes are required for current integrations; authorization is domain-scoped via claims. Audience is managed by QUBS; use the client credentials provisioned for your integration.
Sandbox: no public sandbox environment is currently available.
Boundary errors use RFC7807 Problem Details (application/problem+json). Report workflow failures can instead use application/json; failed final submissions contain operationId, detail and workflowError. Inspect Content-Type before decoding errors. Retain X-Correlation-Id and any operationId for support; a requestId body field is not guaranteed for proxied workflow errors.
Data handling: responses may include PHI (patient name/date of birth). Treat all such data as sensitive, apply least-privilege access, and avoid logging or storing longer than required.
Date/time conventions: date values use ISO 8601 yyyy-MM-dd. time values use HH:mm:ss in the scheduled site's local timezone (see SiteSettings.timeZone). date-time values are returned in UTC. Numeric values are JSON numbers (integers/decimals), not strings.
Pagination: cursor endpoints return page objects with items and nextToken, while offset endpoints return arrays. For cursor paging, stop when nextToken is empty/omitted; for offset paging, stop when the response count is less than take. Ordering is not guaranteed and should not be assumed to be chronological.
Retry guidance: for 429 responses, honor the Retry-After header and use exponential backoff with jitter. GET requests are safe to retry. Report submissions require an Idempotency-Key (up to 128 characters) and retain a durable operation receipt: identical retries replay the recorded outcome, and changed payloads under the same key return 409 Conflict. For network retries, preserve the original key and payload. Comment and attachment-upload endpoints only provide best-effort process-local deduplication with cache lifetimes of up to 24 hours and 15 minutes respectively. Cache eviction, restarts, different instances, or concurrent requests can produce duplicates even when a key is supplied. Do not treat those lifetimes as durable guarantees; verify existing records after an uncertain outcome before retrying. A pending report operation requires reconciliation, not another submission with a new key.
Rate limits: no fixed quotas or standard rate-limit headers are guaranteed. Use 429 responses and Retry-After when present.
- Request an access token using the OAuth 2.0 client credentials flow.
- Send it in the
Authorizationheader asBearer <token>.
Token endpoint: https://authenticate.qubs.io/oauth2/token
Token request examples:
curl -X POST "https://authenticate.qubs.io/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=<client_id>" \
--data-urlencode "client_secret=<client_secret>"Unirest.setTimeouts(0, 0);
HttpResponse<String> response = Unirest.post("https://authenticate.qubs.io/oauth2/token")
.header("Content-Type", "application/x-www-form-urlencoded")
.field("grant_type", "client_credentials")
.field("client_id", "<client_id>")
.field("client_secret", "<client_secret>")
.asString();using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://authenticate.qubs.io/oauth2/token");
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "<client_id>",
["client_secret"] = "<client_secret>"
});
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var tokenPayload = await response.Content.ReadAsStringAsync();Use the access_token value from the response as your bearer token. Cache that token and reuse it across API requests until it is close to the 60-minute expiry; do not request a token for each API call or each record.
Client credentials token responses do not include a refresh-token workflow. When the cached access token is close to expiry, request a new access token with the same client credentials.
curl -X GET "https://partners.qubs.io/integrations/v1/acme/reporting/appointments/pending?pageSize=1" \
-H "Authorization: Bearer <token>"curl -X POST "https://partners.qubs.io/integrations/v1/acme/reporting/appointments/200006/report" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <idempotency-key>" \
-d '{"reportText":"Preliminary report text","reportStatus":"preliminary","interpretingClinicianEmail":"dr.smith@example.com","reportedAtUtc":"2025-01-10T03:04:05Z"}'- List pending reporting appointments.
curl -X GET "https://partners.qubs.io/integrations/v1/acme/reporting/appointments/pending?pageSize=1" \
-H "Authorization: Bearer <token>"Example response:
{
"items": [
{
"domain": "acme",
"appointmentId": "200006",
"status": "PENDING REPORTING",
"scheduledSite": "Central Clinic",
"appointmentDate": "2025-01-12",
"startTime": "13:45:00",
"patientName": "Jane Doe"
}
],
"nextToken": "DDB_NEXT_TOKEN"
}- (Optional) Create an appointment comment/alert while the appointment is still PENDING REPORTING. This reporting endpoint returns 404 after the report moves to TYPED or an authorisation state.
curl -X POST "https://partners.qubs.io/integrations/v1/acme/reporting/appointments/200006/comments" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <idempotency-key>" \
-d '{"commentText":"Partner report preparation started.","commentType":"AC","isAlert":false}'- (Optional) Create report attachment upload URLs.
curl -X POST "https://partners.qubs.io/integrations/v1/acme/reporting/appointments/200006/report/attachments" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <idempotency-key>" \
-d '{"items":[{"fileName":"report.pdf","contentType":"application/pdf"}]}'Example response:
{
"items": [
{
"guid": "989dd0c7-13bb-4bba-b7a0-53a28bdf5155",
"fileName": "report.pdf",
"category": "report",
"uploadUrl": "https://example.com/upload/report.pdf",
"uploadUrlExpiresAtUtc": "2025-01-10T03:19:05Z"
}
]
}- Upload the report artifact bytes with a PUT to
uploadUrl.
curl -X PUT "https://example.com/upload/report.pdf" \
-H "Content-Type: application/pdf" \
--data-binary "@report.pdf"- Submit an editable preliminary report, or send reportStatus final to request normal QUBS authorisation. Supply an active clinician email and the required Idempotency-Key. The partner needs explicit delegation and the clinician needs normal permissions. Attachments are optional. For an existing draft, also send its current reportKey and expectedReportRevision; conflicts return 409. Preliminary saves return 201, while final requests return 202. Inspect workflowStatus; a 202 pending receipt requires reconciliation and must not be repeated with a new key.
curl -X POST "https://partners.qubs.io/integrations/v1/acme/reporting/appointments/200006/report" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <idempotency-key>" \
-d '{"reportText":"No acute abnormality detected.","reportStatus":"preliminary","interpretingClinicianEmail":"dr.smith@example.com","reportedAtUtc":"2025-01-10T03:04:05Z"}'Example response:
{
"reportGuid": "9001",
"reportKey": "PT|220000|9001|RT",
"reportRevision": 1,
"workflowStatus": "TYPED",
"reportStatus": "preliminary",
"reportedAtUtc": "2025-01-10T03:04:05Z"
}Keep reportKey and reportRevision from successful submissions. The partner API currently has no endpoint to retrieve the latest report revision or reconcile a pending operation. After a revision conflict or an unresolved pending response, stop automatic writes and contact QUBS support with the domain, appointmentId, operationId (if returned) and X-Correlation-Id. Do not guess or increment revisions, and do not create a new idempotency key merely to bypass a conflict. Resume only after the current report state and revision have been confirmed through QUBS. Identical network retries must reuse the original key and payload.