Skip to content

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, and expires_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.

Getting started

Base URL

  • Production: https://partners.qubs.io

Authentication

  1. Request an access token using the OAuth 2.0 client credentials flow.
  2. Send it in the Authorization header as Bearer <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.

Example requests

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"}'

Reporting workflow (example)

  1. 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"
}
  1. (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}'
  1. (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"
    }
  ]
}
  1. 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"
  1. 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"
}

Conflict and pending-operation recovery

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.

Download OpenAPI description
Languages
Servers
Mock server
https://partnerdocs.qubs.io/_mock/openapi
Production API
https://partners.qubs.io
Relative to the current host
https://partnerdocs.qubs.io