
Developer preview · API access is not publicly enabled
Build with CliniBase
Use selected practice knowledge. Return operational drafts for people to review.
REST and MCP share the same permissions. CliniBase keeps ownership, review and the authoritative work record.
Local technical verification is complete for these interfaces. Independent security review, live provider canaries and human pilot review are pending. All example hosts are reserved .example addresses.
Quickstart
- Register your app. Request preview access with your app name, exact HTTPS callback, requested scopes and support/privacy contacts. Registration is curated; there is no public registration endpoint.
- Use a fictional practice. Its owner permits your registered app, sets a scope ceiling and shares exact eligible versions. No sources are selected implicitly.
- Link one member. Start OAuth with PKCE S256 and state. The member signs in, chooses one practice, narrower scopes and specific sources. Background access is a separate owner allowance and member choice.
- Read and contribute. Get connection context, fetch permitted evidence and submit a bounded draft with a UUID idempotency key. Inspect the receipt; open its signed-in review link.
- Handle changes. Respect expiry, scope denial, source changes and quotas. Stop on revocation. Request new consent only through an explicit user action.
Use Ask Clini for sourced answers and confirmed actions inside CliniBase. Use an external AI tool when you are already there brainstorming or writing and want selected practice context and a draft handoff.
Permissions
| Scope | What it permits |
|---|---|
knowledge:read | Read explicitly selected current approved guides and procedures. |
tasks:self:read | Read your own eligible operational tasks. |
summaries:read | Read saved monthly counts as an owner or manager. Unsupported and suppressed values stay null. |
drafts:write | Submit immutable text proposals for human review. |
drafts:self:read | Inspect this member and app’s proposals and withdraw eligible pending work. |
Effective access is the intersection of registered app scopes, owner ceilings, member consent, token scopes and current native permissions. Owner approval gives a maximum; each member still chooses less. offline_access is not a business scope: use the separate native background consent control.
No patient records, clinical notes, sensitive HR information, raw Library originals, private chats, staff directory, booking details, publishing, approval, deletion, permission changes or PMS writes.
Authentication
OAuth authorisation code with PKCE S256. External APIs accept only their dedicated bearer; native cookies and CliniBase session tokens cannot authenticate REST or MCP. Store secrets in your client’s protected credential store.
- Protected resource
https://api.clinibase.example/api/externalfor both transports- Discovery
/.well-known/oauth-protected-resource/api/externaland/.well-known/oauth-authorization-server- Authorisation
GET /oauth/authorizewith client_id, exact redirect_uri, response_type=code, resource, scope, random state, code_challenge and code_challenge_method=S256. This opens trusted native consent.- Token exchange
POST /oauth/tokenas form data: grant_type=authorization_code, code, code_verifier, exact redirect_uri, client_id and the same resource. Confidential clients use their registered authentication method. Validate returned state and issuer before exchange.- Refresh
grant_type=refresh_tokenwith refresh_token, registered client authentication and the same resource. Only explicit offline grants issue refresh credentials. Atomically replace each rotated refresh token; never reuse it. Concurrent refresh attempts must be serialized in your client.- Revocation
POST /oauth/revokewith token and registered client authentication. Owners and members can also disconnect in Settings → Connections.
Code: 60 seconds, single use. Access token: 10 minutes. Refresh: 30-day idle limit. Grant: 90-day absolute maximum, including background grants. Refresh cannot add scopes or extend the grant indefinitely. A revoked grant denies the next admission; already delivered information cannot be recalled.
V1 excludes dynamic registration, client credentials, password grants and shared practice identities. Provider client-registration compatibility still needs validation; do not substitute a shared key when OAuth linking is unsupported.
REST reference
Base path: /api/external/v1. JSON responses are unwrapped and private/no-store. Use Authorization: Bearer …. Draft submission needs Idempotency-Key; withdrawal also needs a strong If-Match ETag.
GET /connection · Inspect the current one-practice connection.
Active grant required; no additional business scope. No member directory or credentials.
Operation: getConnection
{
"responses": {
"200": {
"$ref": "#/components/responses/Connection"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}GET /knowledge · Search or list eligible selected current approved knowledge.
Filter permissions, selected sources, dependencies and external eligibility before searching/snippets. No inaccessible record count, draft, superseded revision or raw Library payload. Omit q to list. Unknown parameters rejected.
Operation: searchKnowledge
{
"parameters": [
{
"name": "q",
"in": "query",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"description": "Optional operational search text. Do not include personal/sensitive information."
},
{
"$ref": "#/components/parameters/Cursor"
},
{
"$ref": "#/components/parameters/Limit"
}
],
"security": [
{
"externalOAuth": [
"knowledge:read"
]
}
],
"responses": {
"200": {
"$ref": "#/components/responses/KnowledgeList"
},
"400": {
"$ref": "#/components/responses/InvalidRequest"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}GET /knowledge/{knowledge_id} · Read one current approved externally eligible revision.
Missing, denied, unpublished or no-longer-current identifiers all yield generic 404.
Operation: getKnowledge
{
"parameters": [
{
"name": "knowledge_id",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/OpaqueId"
}
}
],
"security": [
{
"externalOAuth": [
"knowledge:read"
]
}
],
"responses": {
"200": {
"$ref": "#/components/responses/KnowledgeDetail"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}GET /tasks/mine · List externally eligible tasks owned by this member.
Default active statuses are planned/in_progress/blocked/in_review. No other owner filter. Excludes provider-derived reminders, identities, history bodies, deliverables and completion notes. Unknown parameters rejected.
Operation: listMyTasks
{
"parameters": [
{
"name": "status",
"in": "query",
"schema": {
"$ref": "#/components/schemas/TaskState"
}
},
{
"$ref": "#/components/parameters/Cursor"
},
{
"$ref": "#/components/parameters/Limit"
}
],
"security": [
{
"externalOAuth": [
"tasks:self:read"
]
}
],
"responses": {
"200": {
"$ref": "#/components/responses/TaskList"
},
"400": {
"$ref": "#/components/responses/InvalidRequest"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}GET /summaries/activity · Read eligible saved monthly practice-wide activity totals.
Owners/managers only, with current consent and externally eligible saved projection. Completed calendar months in practice timezone, maximum 12 inclusive months. No practitioner/location/person filters. No live PMS call, sync or AI generation. Unsupported, suppressed or insufficiently covered values are null, never fabricated zero. Proposed suppression floor is fewer than five underlying appointments with additional differencing/metric checks; not an anonymisation guarantee. No revenue, causal or staff-ranking claims.
Operation: getActivitySummary
{
"parameters": [
{
"name": "provider",
"in": "query",
"required": true,
"schema": {
"$ref": "#/components/schemas/Provider"
}
},
{
"name": "from_month",
"in": "query",
"required": true,
"schema": {
"$ref": "#/components/schemas/Month"
}
},
{
"name": "to_month",
"in": "query",
"required": true,
"schema": {
"$ref": "#/components/schemas/Month"
}
}
],
"security": [
{
"externalOAuth": [
"summaries:read"
]
}
],
"responses": {
"200": {
"$ref": "#/components/responses/ActivitySummary"
},
"400": {
"$ref": "#/components/responses/InvalidRequest"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}POST /drafts · Submit an immutable text proposal for human review.
Creates an intake submission and receipt, not a canonical record or approved source. JSON <=64 KiB UTF-8; all character limits also apply. No file upload, arbitrary URL fetch or approval/assignment/audience/announcement fields. Nonempty source_refs additionally requires knowledge:read; missing scope returns 403 insufficient_scope BEFORE resolving any source ID. Omitted or empty refs allow drafts:write-only submission without verified evidence. With read scope present, pins must be currently selected, approved, permitted and unchanged. An accessible stale pin gives 409 source_changed; denied/missing pins give 404. Replacement requires supersedes_draft_id and supersedes_etag; locks/rechecks an own same-client pending/changes_requested predecessor and atomically withdraws it only if replacement succeeds. Native intake may have tighter limits (current Practice Hub title 160 versus proposed intake 200). Humans correct these explicitly; never truncate silently. Default contribution consent pairs drafts:write with drafts:self:read. A write-only grant can submit, but cannot obtain fresh detail ETags for replacement/withdrawal and uses native CliniBase for follow-up. Internal request fingerprints are never public receipt fields.
Operation: submitDraft
{
"parameters": [
{
"$ref": "#/components/parameters/IdempotencyKey"
}
],
"security": [
{
"externalOAuth": [
"drafts:write"
]
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DraftSubmission"
},
"example": {
"kind": "guide",
"title": "A clearer end-of-day handover",
"body": "List outstanding operational work, its next owner and next check date before leaving.",
"source_refs": [
{
"knowledge_id": "kn_example_handover",
"version": "ver_example_03"
}
],
"provenance_note": "Prepared in an external writing tool for human review."
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/SubmissionReceipt"
},
"201": {
"$ref": "#/components/responses/SubmissionReceipt"
},
"400": {
"$ref": "#/components/responses/InvalidRequest"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"409": {
"$ref": "#/components/responses/Conflict"
},
"412": {
"$ref": "#/components/responses/PreconditionFailed"
},
"413": {
"$ref": "#/components/responses/TooLarge"
},
"415": {
"$ref": "#/components/responses/UnsupportedMedia"
},
"422": {
"$ref": "#/components/responses/ContentNotAllowed"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}GET /drafts · List own member-plus-client submission metadata.
Same member and registered client under an active same-practice grant only. No practice-wide inbox or other-client history. Reconnect does not restore revoked source permissions. Current dependency/payload access still applies. Filter before titles, counts or cursor construction; conceal inaccessible submissions as in detail reads.
Operation: listMyDrafts
{
"parameters": [
{
"name": "status",
"in": "query",
"schema": {
"$ref": "#/components/schemas/DraftState"
}
},
{
"$ref": "#/components/parameters/Cursor"
},
{
"$ref": "#/components/parameters/Limit"
}
],
"security": [
{
"externalOAuth": [
"drafts:self:read"
]
}
],
"responses": {
"200": {
"$ref": "#/components/responses/DraftList"
},
"400": {
"$ref": "#/components/responses/InvalidRequest"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}GET /drafts/{draft_id} · Read an own accessible submission and current status.
ETag validates mutable intake state; immutable revision identifies payload. accepted means human native intake, NOT published/assigned/completed. No reviewer identity or private free-form reviewer notes. Optional native target reference is returned only if currently permitted.
Operation: getMyDraft
{
"parameters": [
{
"$ref": "#/components/parameters/DraftId"
}
],
"security": [
{
"externalOAuth": [
"drafts:self:read"
]
}
],
"responses": {
"200": {
"$ref": "#/components/responses/DraftDetail"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}POST /drafts/{draft_id}/withdraw · Withdraw one own pending/revision-requested submission.
Does not delete a receipt or affect canonical work. Accepted/rejected/ withdrawn/expired states cannot transition. Current authorization and ownership are checked before state/precondition diagnostics. Idempotent retries require the same key and original If-Match value.
Operation: withdrawMyDraft
{
"parameters": [
{
"$ref": "#/components/parameters/DraftId"
},
{
"$ref": "#/components/parameters/IdempotencyKey"
},
{
"name": "If-Match",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/EntityTag"
},
"description": "Exact quoted current ETag from GET draft detail; wildcard is forbidden."
}
],
"security": [
{
"externalOAuth": [
"drafts:write"
]
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"maxProperties": 0
},
"example": {}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/WithdrawnDraft"
},
"400": {
"$ref": "#/components/responses/InvalidRequest"
},
"401": {
"$ref": "#/components/responses/Unauthenticated"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"409": {
"$ref": "#/components/responses/Conflict"
},
"412": {
"$ref": "#/components/responses/PreconditionFailed"
},
"413": {
"$ref": "#/components/responses/TooLarge"
},
"415": {
"$ref": "#/components/responses/UnsupportedMedia"
},
"428": {
"$ref": "#/components/responses/PreconditionRequired"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/Unavailable"
},
"default": {
"$ref": "#/components/responses/SafeError"
}
}
}Full field schemas, limits and response shapes
{
"securitySchemes": {
"externalOAuth": {
"type": "oauth2",
"description": "Proposed authorization-code + PKCE S256. One active member, one practice,\none registered client and selected sources. Opaque short-lived external\nresource token; NEVER a browser session. Current revocation checked live.\nActual issuer/resource/registration mechanisms are milestone-1 decisions.\n",
"flows": {
"authorizationCode": {
"authorizationUrl": "https://app.clinibase.example/oauth/authorize",
"tokenUrl": "https://app.clinibase.example/oauth/token",
"refreshUrl": "https://app.clinibase.example/oauth/token",
"scopes": {
"knowledge:read": "Read selected current approved externally eligible knowledge.",
"tasks:self:read": "Read eligible minimal tasks owned by this member.",
"summaries:read": "Read eligible saved monthly practice totals as owner/manager.",
"drafts:write": "Submit/withdraw eligible own intake proposals; no canonical writes.",
"drafts:self:read": "Read own member-plus-client accessible submissions."
}
}
}
}
},
"parameters": {
"Cursor": {
"name": "cursor",
"in": "query",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 2048
},
"description": "Opaque cursor bound to current grant/filter/source snapshot; invalidated by relevant changes."
},
"Limit": {
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 20
}
},
"DraftId": {
"name": "draft_id",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/OpaqueId"
}
},
"IdempotencyKey": {
"name": "Idempotency-Key",
"in": "header",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "Random UUID unique per intended operation. Same key/body/target/precondition\nfor retry, proposed 24-hour replay window. Scoped by practice/member/client/\noperation. Changed payload yields 409, revoked permission never replays content.\n"
}
},
"headers": {
"CacheControl": {
"description": "All business results and errors are private and must not be stored in shared caches.",
"schema": {
"type": "string",
"const": "private, no-store"
}
},
"RequestId": {
"description": "Safe correlation reference; never a token or request body.",
"schema": {
"$ref": "#/components/schemas/OpaqueId"
}
},
"ETag": {
"description": "Strong opaque validator for current intake state; no content digest.",
"schema": {
"$ref": "#/components/schemas/EntityTag"
}
},
"IdempotencyReplayed": {
"description": "True if this result safely replays a successful prior intended operation.",
"schema": {
"type": "boolean"
}
},
"Location": {
"description": "Server-generated status resource URL; requires current scope/access.",
"schema": {
"type": "string",
"format": "uri"
}
},
"RetryAfter": {
"description": "Proposed integer delay in seconds; use bounded backoff.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 3600
}
},
"Authenticate": {
"description": "Safe bearer challenge; resource_metadata supports protected-resource discovery.",
"schema": {
"type": "string",
"maxLength": 2048
}
}
},
"schemas": {
"OpaqueId": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"pattern": "^[A-Za-z0-9_-]+$",
"description": "Opaque case-sensitive reference, not a native numeric database ID."
},
"EntityTag": {
"type": "string",
"minLength": 3,
"maxLength": 132,
"pattern": "^\"[A-Za-z0-9_-]+\"$",
"description": "Exact strong quoted validator; weak validators and wildcard are not accepted."
},
"Month": {
"type": "string",
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$"
},
"Provider": {
"type": "string",
"enum": [
"cliniko",
"nookal"
]
},
"DraftKind": {
"type": "string",
"enum": [
"guide",
"procedure",
"training",
"team_update",
"task",
"reference"
]
},
"DraftState": {
"type": "string",
"enum": [
"pending_review",
"changes_requested",
"accepted",
"rejected",
"withdrawn",
"expired"
],
"description": "accepted is human native intake only, not publication or completion."
},
"TaskState": {
"type": "string",
"enum": [
"planned",
"in_progress",
"blocked",
"in_review",
"completed",
"cancelled"
]
},
"SourceUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Server-generated CliniBase link; native sign-in/current permissions required."
},
"SourceRef": {
"type": "object",
"additionalProperties": false,
"required": [
"knowledge_id",
"version"
],
"properties": {
"knowledge_id": {
"$ref": "#/components/schemas/OpaqueId"
},
"version": {
"$ref": "#/components/schemas/OpaqueId"
}
}
},
"Connection": {
"type": "object",
"required": [
"connection_id",
"practice",
"client",
"scopes",
"allowed_draft_kinds",
"consent_expires_at",
"api_version"
],
"properties": {
"connection_id": {
"$ref": "#/components/schemas/OpaqueId"
},
"practice": {
"type": "object",
"required": [
"ref",
"name",
"timezone"
],
"properties": {
"ref": {
"$ref": "#/components/schemas/OpaqueId"
},
"name": {
"type": "string",
"maxLength": 200
},
"timezone": {
"type": "string",
"maxLength": 100,
"examples": [
"Australia/Melbourne"
]
}
}
},
"client": {
"type": "object",
"required": [
"ref",
"name"
],
"properties": {
"ref": {
"$ref": "#/components/schemas/OpaqueId"
},
"name": {
"type": "string",
"maxLength": 200
}
}
},
"scopes": {
"type": "array",
"uniqueItems": true,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"knowledge:read",
"tasks:self:read",
"summaries:read",
"drafts:write",
"drafts:self:read"
]
}
},
"allowed_draft_kinds": {
"type": "array",
"uniqueItems": true,
"maxItems": 6,
"items": {
"$ref": "#/components/schemas/DraftKind"
}
},
"consent_expires_at": {
"type": "string",
"format": "date-time"
},
"api_version": {
"type": "string",
"const": "v1"
}
}
},
"KnowledgeListItem": {
"type": "object",
"required": [
"id",
"kind",
"title",
"version",
"excerpt",
"approved_at",
"source_url"
],
"properties": {
"id": {
"$ref": "#/components/schemas/OpaqueId"
},
"kind": {
"type": "string",
"enum": [
"guide",
"procedure"
]
},
"title": {
"type": "string",
"maxLength": 200
},
"version": {
"$ref": "#/components/schemas/OpaqueId"
},
"excerpt": {
"type": "string",
"maxLength": 500
},
"approved_at": {
"type": "string",
"format": "date-time"
},
"source_url": {
"$ref": "#/components/schemas/SourceUrl"
}
}
},
"KnowledgeDetail": {
"type": "object",
"required": [
"id",
"kind",
"title",
"version",
"body",
"approved_at",
"updated_at",
"source_url"
],
"properties": {
"id": {
"$ref": "#/components/schemas/OpaqueId"
},
"kind": {
"type": "string",
"enum": [
"guide",
"procedure"
]
},
"title": {
"type": "string",
"maxLength": 200
},
"version": {
"$ref": "#/components/schemas/OpaqueId"
},
"body": {
"type": "string",
"minLength": 1,
"maxLength": 20000
},
"approved_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"source_url": {
"$ref": "#/components/schemas/SourceUrl"
}
}
},
"KnowledgeList": {
"type": "object",
"required": [
"items",
"next_cursor"
],
"properties": {
"items": {
"type": "array",
"maxItems": 50,
"items": {
"$ref": "#/components/schemas/KnowledgeListItem"
}
},
"next_cursor": {
"type": [
"string",
"null"
],
"maxLength": 2048
}
}
},
"TaskListItem": {
"type": "object",
"required": [
"id",
"title",
"definition_of_done",
"status",
"target_date",
"updated_at",
"source_url"
],
"properties": {
"id": {
"$ref": "#/components/schemas/OpaqueId"
},
"title": {
"type": "string",
"maxLength": 200
},
"definition_of_done": {
"type": "string",
"maxLength": 5000
},
"status": {
"$ref": "#/components/schemas/TaskState"
},
"target_date": {
"type": [
"string",
"null"
],
"format": "date"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"source_url": {
"$ref": "#/components/schemas/SourceUrl"
}
}
},
"TaskList": {
"type": "object",
"required": [
"items",
"next_cursor"
],
"properties": {
"items": {
"type": "array",
"maxItems": 50,
"items": {
"$ref": "#/components/schemas/TaskListItem"
}
},
"next_cursor": {
"type": [
"string",
"null"
],
"maxLength": 2048
}
}
},
"ActivityMetric": {
"type": "object",
"required": [
"key",
"unit",
"availability",
"value"
],
"properties": {
"key": {
"type": "string",
"enum": [
"appointment_count",
"attended_count",
"cancelled_count",
"did_not_attend_count",
"booked_minutes",
"available_minutes",
"utilisation_percent"
]
},
"unit": {
"type": "string",
"enum": [
"count",
"minutes",
"percent"
]
},
"availability": {
"type": "string",
"enum": [
"available",
"unsupported",
"suppressed",
"insufficient_coverage",
"unavailable"
]
},
"value": {
"type": [
"number",
"null"
],
"minimum": 0
}
},
"allOf": [
{
"if": {
"properties": {
"availability": {
"const": "available"
}
}
},
"then": {
"properties": {
"value": {
"type": "number",
"minimum": 0
}
}
},
"else": {
"properties": {
"value": {
"type": "null"
}
}
}
}
],
"description": "Null for every unavailable/suppressed metric. Metric keys are a proposed\nsafe projection, not a promise of provider support. No suppressed denominator.\nUnits must match keys; preserve supported values without silently clamping\nutilisation to 100 when booked time exceeds capacity. Validate in service tests.\n"
},
"ActivityMonth": {
"type": "object",
"required": [
"month",
"coverage",
"freshness",
"metrics"
],
"properties": {
"month": {
"$ref": "#/components/schemas/Month"
},
"coverage": {
"type": "string",
"enum": [
"complete",
"partial",
"missing"
]
},
"freshness": {
"type": "string",
"enum": [
"current",
"stale",
"unavailable"
]
},
"metrics": {
"type": "array",
"maxItems": 7,
"items": {
"$ref": "#/components/schemas/ActivityMetric"
}
}
},
"description": "Unique metric keys; no raw rows, member/location identities or appointment IDs."
},
"ActivitySummary": {
"type": "object",
"required": [
"provider",
"timezone",
"from_month",
"to_month",
"last_successful_sync_at",
"generated_at",
"months"
],
"properties": {
"provider": {
"$ref": "#/components/schemas/Provider"
},
"timezone": {
"type": "string",
"maxLength": 100
},
"from_month": {
"$ref": "#/components/schemas/Month"
},
"to_month": {
"$ref": "#/components/schemas/Month"
},
"last_successful_sync_at": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"generated_at": {
"type": "string",
"format": "date-time"
},
"months": {
"type": "array",
"minItems": 1,
"maxItems": 12,
"items": {
"$ref": "#/components/schemas/ActivityMonth"
}
}
},
"description": "One row per requested completed month, even if missing. Nookal activity\ncapacity remains unsupported; no identities, money, inferred causation,\nclinical quality, staff scores or auto-recommendation text.\n"
},
"DraftSubmission": {
"type": "object",
"additionalProperties": false,
"required": [
"kind",
"title",
"body"
],
"properties": {
"kind": {
"$ref": "#/components/schemas/DraftKind"
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"body": {
"type": "string",
"minLength": 1,
"maxLength": 20000
},
"source_refs": {
"type": "array",
"maxItems": 20,
"uniqueItems": true,
"items": {
"$ref": "#/components/schemas/SourceRef"
},
"description": "Nonempty array requires knowledge:read before ID resolution; empty/omitted array allows write-only submission without verified evidence."
},
"provenance_note": {
"type": "string",
"minLength": 1,
"maxLength": 2000
},
"supersedes_draft_id": {
"$ref": "#/components/schemas/OpaqueId"
},
"supersedes_etag": {
"$ref": "#/components/schemas/EntityTag"
},
"reference_url": {
"type": "string",
"format": "uri",
"pattern": "^https://",
"maxLength": 2048,
"description": "Reference kind only; inert HTTPS metadata without embedded credentials; NEVER fetched or approved evidence."
}
},
"dependentRequired": {
"supersedes_draft_id": [
"supersedes_etag"
],
"supersedes_etag": [
"supersedes_draft_id"
]
},
"oneOf": [
{
"properties": {
"kind": {
"const": "reference"
}
}
},
{
"properties": {
"kind": {
"enum": [
"guide",
"procedure",
"training",
"team_update",
"task"
]
}
},
"not": {
"required": [
"reference_url"
]
}
}
],
"description": "JSON 64-KiB byte cap is enforced in gateway in addition to character limits.\nTrim/nonblank/operational/privacy guards apply; no duplicate knowledge_id\npins even with different versions. No arbitrary instructions, files or\ncanonical author/owner/reviewer/audience/approval/announcement fields.\n"
},
"SubmissionReceipt": {
"type": "object",
"required": [
"receipt_id",
"draft_id",
"revision",
"kind",
"submitted_at",
"status_url",
"review_url"
],
"properties": {
"receipt_id": {
"$ref": "#/components/schemas/OpaqueId"
},
"draft_id": {
"$ref": "#/components/schemas/OpaqueId"
},
"revision": {
"type": "integer",
"minimum": 1
},
"kind": {
"$ref": "#/components/schemas/DraftKind"
},
"submitted_at": {
"type": "string",
"format": "date-time"
},
"status_url": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"review_url": {
"$ref": "#/components/schemas/SourceUrl"
}
},
"description": "Immutable public receipt, no submitted text/fingerprint and no approval or current-state claim."
},
"DraftListItem": {
"type": "object",
"required": [
"id",
"revision",
"kind",
"title",
"status",
"submitted_at",
"expires_at",
"updated_at",
"review_url"
],
"properties": {
"id": {
"$ref": "#/components/schemas/OpaqueId"
},
"revision": {
"type": "integer",
"minimum": 1
},
"kind": {
"$ref": "#/components/schemas/DraftKind"
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"status": {
"$ref": "#/components/schemas/DraftState"
},
"submitted_at": {
"type": "string",
"format": "date-time"
},
"expires_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"review_url": {
"$ref": "#/components/schemas/SourceUrl"
}
}
},
"DraftDetail": {
"allOf": [
{
"$ref": "#/components/schemas/DraftListItem"
},
{
"type": "object",
"required": [
"body",
"source_refs",
"provenance_note",
"reference_url",
"supersedes_draft_id",
"feedback_code",
"target"
],
"properties": {
"body": {
"type": "string",
"minLength": 1,
"maxLength": 20000
},
"source_refs": {
"type": "array",
"maxItems": 20,
"items": {
"$ref": "#/components/schemas/SourceRef"
}
},
"provenance_note": {
"type": [
"string",
"null"
],
"maxLength": 2000
},
"reference_url": {
"type": [
"string",
"null"
],
"format": "uri",
"maxLength": 2048
},
"supersedes_draft_id": {
"oneOf": [
{
"$ref": "#/components/schemas/OpaqueId"
},
{
"type": "null"
}
]
},
"feedback_code": {
"type": [
"string",
"null"
],
"enum": [
"needs_clarification",
"sources_changed",
"unsuitable_content",
"duplicate_proposal",
"outside_scope",
"superseded",
null
],
"description": "Safe category only; no reviewer identity or private reviewer prose."
},
"target": {
"oneOf": [
{
"type": "object",
"required": [
"kind",
"ref"
],
"properties": {
"kind": {
"$ref": "#/components/schemas/DraftKind"
},
"ref": {
"$ref": "#/components/schemas/OpaqueId"
}
}
},
{
"type": "null"
}
],
"description": "Native target reference only after intake and only if currently permitted."
}
}
}
]
},
"DraftList": {
"type": "object",
"required": [
"items",
"next_cursor"
],
"properties": {
"items": {
"type": "array",
"maxItems": 50,
"items": {
"$ref": "#/components/schemas/DraftListItem"
}
},
"next_cursor": {
"type": [
"string",
"null"
],
"maxLength": 2048
}
}
},
"WithdrawnDraft": {
"type": "object",
"required": [
"id",
"revision",
"status",
"updated_at"
],
"properties": {
"id": {
"$ref": "#/components/schemas/OpaqueId"
},
"revision": {
"type": "integer",
"minimum": 1
},
"status": {
"type": "string",
"const": "withdrawn"
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
},
"ErrorEnvelope": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"retryable",
"request_id"
],
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthenticated",
"insufficient_scope",
"connection_unavailable",
"not_found",
"idempotency_conflict",
"draft_state_conflict",
"source_changed",
"precondition_failed",
"precondition_required",
"content_not_allowed",
"request_too_large",
"unsupported_media_type",
"rate_limited",
"service_unavailable"
]
},
"message": {
"type": "string",
"maxLength": 240
},
"retryable": {
"type": "boolean"
},
"request_id": {
"$ref": "#/components/schemas/OpaqueId"
},
"fields": {
"type": "array",
"maxItems": 20,
"items": {
"type": "object",
"required": [
"path",
"code"
],
"properties": {
"path": {
"type": "string",
"maxLength": 256
},
"code": {
"type": "string",
"enum": [
"unknown_field",
"invalid_format",
"blank",
"too_long",
"invalid_pair",
"limit_exceeded"
]
}
}
}
}
}
}
},
"description": "Generic safe messages/field paths only; never echo input, source title, query, token or inaccessible existence."
}
},
"responses": {
"Connection": {
"description": "Current safe connection metadata.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Connection"
}
}
}
},
"KnowledgeList": {
"description": "Permitted eligible result page; an empty list is a valid result.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/KnowledgeList"
}
}
}
},
"KnowledgeDetail": {
"description": "Current approved eligible content; never silently truncate a full guidance body.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/KnowledgeDetail"
}
}
}
},
"TaskList": {
"description": "Permitted externally eligible own tasks.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TaskList"
}
}
}
},
"ActivitySummary": {
"description": "Eligible saved monthly totals with honest support, suppression, coverage and freshness.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ActivitySummary"
}
}
}
},
"SubmissionReceipt": {
"description": "Immutable new-submission receipt, or safe same-key replay. No canonical record created.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
},
"Location": {
"$ref": "#/components/headers/Location"
},
"Idempotency-Replayed": {
"$ref": "#/components/headers/IdempotencyReplayed"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubmissionReceipt"
}
}
}
},
"DraftList": {
"description": "Own member-plus-client submission metadata page; no bodies.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DraftList"
}
}
}
},
"DraftDetail": {
"description": "Own currently accessible payload and state; accepted is not published/completed.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
},
"ETag": {
"$ref": "#/components/headers/ETag"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DraftDetail"
}
}
}
},
"WithdrawnDraft": {
"description": "Own pending submission withdrawn, or safe same-key replay. No canonical work affected.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
},
"ETag": {
"$ref": "#/components/headers/ETag"
},
"Idempotency-Replayed": {
"$ref": "#/components/headers/IdempotencyReplayed"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WithdrawnDraft"
}
}
}
},
"SafeError": {
"description": "Safe documented error envelope; no sensitive content or record existence leakage.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"InvalidRequest": {
"description": "invalid_request. Invalid structure/unknown fields/filters/cursor. Not retryable.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"Unauthenticated": {
"description": "unauthenticated. Missing/invalid/expired/wrong-audience/revoked external token; never accept browser cookies.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
},
"WWW-Authenticate": {
"$ref": "#/components/headers/Authenticate"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"Forbidden": {
"description": "insufficient_scope or connection_unavailable. Valid external principal denied by current scope/role/feature/sharing policy; revoked tokens use 401.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
},
"WWW-Authenticate": {
"$ref": "#/components/headers/Authenticate"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"NotFound": {
"description": "not_found. Missing and denied resources indistinguishable; no identifier enumeration.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"Conflict": {
"description": "idempotency_conflict, draft_state_conflict or source_changed. Human review/re-read required; not retryable.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"PreconditionFailed": {
"description": "precondition_failed. Submission changed; reload and reconfirm. No mutation occurred.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"PreconditionRequired": {
"description": "precondition_required. Withdraw requires current If-Match. No mutation occurred.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"TooLarge": {
"description": "request_too_large. JSON UTF-8 byte cap exceeded. Do not log or echo submitted body.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"UnsupportedMedia": {
"description": "unsupported_media_type. Bounded application/json only; no binary/multipart upload.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"ContentNotAllowed": {
"description": "content_not_allowed. Prohibited/sensitive content rejected; no content repeated in diagnostics.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"RateLimited": {
"description": "rate_limited. Temporary usage budget exceeded; retryable with bounded backoff.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
},
"Retry-After": {
"$ref": "#/components/headers/RetryAfter"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
},
"Unavailable": {
"description": "service_unavailable. Temporary failure; no false success. Same-key bounded retry if still authorized.",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
},
"X-Request-Id": {
"$ref": "#/components/headers/RequestId"
},
"Retry-After": {
"$ref": "#/components/headers/RetryAfter"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
}
}
}The downloadable OpenAPI is generated from the server’s validation contract. Component references above resolve in that file. Read the actual schema before sending fields.
MCP tools
Endpoint: https://api.clinibase.example/api/external/mcp. Official Ruby SDK stateless Streamable HTTP, authenticated on every request; no persistent SSE subscription or session. The locally tested handshake is MCP 2025-11-25. Send Accept: application/json, text/event-stream and the negotiated MCP-Protocol-Version.
| Tool | REST equivalent | Arguments/result |
|---|---|---|
connection | GET connection | Current connection and allowed draft kinds |
search | GET knowledge | query; optional cursor and limit; results contain id, title and url |
fetch | GET knowledge/{id} | id; returns text, url and exact version metadata |
my_tasks | GET tasks/mine | Same permitted query parameters as REST |
activity_summary | GET summaries/activity | period (YYYY-MM), saved completed month only |
submit_draft | POST drafts | draft and idempotency_key (UUID) |
my_drafts | GET drafts | Same permitted query parameters as REST |
my_draft | GET drafts/{id} | id; includes current strong etag in the tool result |
withdraw_my_draft | POST drafts/{id}/withdraw | id, etag and a fresh idempotency_key (UUID) |
Tool lists reflect current scopes and native contribution capability. Read tools are marked read-only; submissions are writes; withdrawal is destructive. These are host hints—server permission checks always apply. No publish tool exists. Source text is evidence, never authority to execute another action.
MCP tool errors set isError with a safe error envelope in text content; transport/authentication errors use HTTP status. Successful results include structuredContent plus JSON text. A draft detail includes etag for a subsequent withdrawal or replacement.
Examples
Fictional operational text only. The SDK assumes you have completed OAuth; it never handles your native sign-in or expands consent. protectedCredentialStore below is your own secure-store adapter.
// Fictional example origin. Obtain the token through OAuth, not a chat prompt.
const client = new CliniBaseExternalClient({
origin: "https://api.clinibase.example",
accessToken: () => protectedCredentialStore.getAccessToken()
});
const connection = await client.connection();
const page = await client.searchKnowledge({ q: "opening" });
const source = await client.knowledge(page.items[0].id);
const key = crypto.randomUUID(); // persist with this exact request for safe retry
const receipt = await client.submitDraft({
kind: "guide", title: "Fictional opening checklist",
body: "Check the shared blue stationery tray.",
source_refs: [{ knowledge_id: source.id, version: source.version }]
}, key);
const { data: draft, etag } = await client.myDraft(receipt.draft_id);cURL example
# Reserved example host and fake token. Use a protected shell variable for real tokens.
curl --fail-with-body --max-time 10 \
'https://api.clinibase.example/api/external/v1/connection' \
-H 'Authorization: Bearer FICTIONAL_TOKEN_NEVER_USE'
curl --fail-with-body --max-time 10 \
'https://api.clinibase.example/api/external/v1/drafts' \
-H 'Authorization: Bearer FICTIONAL_TOKEN_NEVER_USE' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 00000000-0000-4000-8000-000000000001' \
--data '{"kind":"task","title":"Check the stationery tray","body":"Confirm the blue tray is ready."}'MCP submission example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "submit_draft",
"arguments": {
"idempotency_key": "00000000-0000-4000-8000-000000000001",
"draft": {
"kind": "guide",
"title": "Fictional opening checklist",
"body": "Check the shared blue stationery tray."
}
}
}
}Persist the UUID and exact request before sending. After a lost acknowledgement, retry the same request and key within 24 hours. A different body under that key returns 409. The SDK makes one bounded attempt, follows no redirects and never retries writes automatically. After replay expiry, inspect your submitted drafts and resolve the uncertain result before creating another operation.
A replacement submits a new immutable draft with both supersedes_draft_id and supersedes_etag, plus a fresh key. It atomically withdraws your eligible predecessor. REST withdrawal sends an empty JSON object, a fresh UUID key and the current ETag.
Contribution review
Six kinds: guide, procedure, training, team_update, task and reference. A receipt proves intake only. Status moves from pending_review to changes_requested, accepted, rejected, withdrawn or expired. A changes request needs a replacement submission.
- Guides, procedures and training become native working drafts with independent approval. Neither the app contributor nor the human curator can independently approve their work.
- Tasks become planned work with a human-selected owner and required review. Acceptance never completes a task.
- Team updates enter the practice channel only after a person confirms the text and mentions.
- References become pending Library material; another permitted reviewer must allow their use. Reference URLs are inert metadata and are never fetched.
Only owners/managers can accept in the native inbox. People choose owners, dates, audiences and dependencies. Current grants and exact evidence are checked again. A permitted opaque native target may appear after acceptance; the external API does not export that native record’s contents.
Limits and errors
| Limit | Preview bound |
|---|---|
| Reads | 60/minute per member/app, across grants and REST/MCP |
| Writes | 10/minute; failed authenticated attempts also count |
| New drafts | 50/day per member/app and per grant; 120/day per practice; 50 pending per practice |
| Input | 64 KiB JSON; title 200, body 20,000, provenance 2,000 characters; 20 unique source pins |
| Pages | 20 default, 50 maximum; opaque bound cursors expire after 10 minutes |
| Concurrency | 5 per grant, 20 per practice; safe unavailable response at capacity |
| Work budget | 5-second admitted-work/statement checks; database lock waits capped at 250 ms |
| Response | 128 KiB REST, bounded MCP encoding overhead |
| Sources | 100 explicitly shared versions; unsupported dependency graphs are excluded |
| Proposal transition expiry | 30 days; replay record 24 hours |
Native intake limits may be narrower: guide/procedure titles 160; procedure purpose 4,000 and 1–30 steps; task/team text 5,000. Queries and responses are bounded. Proposal-body removal after thirty terminal days is implemented but stays off pending retention approval; immutable receipt and evidence references remain. Monthly summaries have five-person suppression and differencing controls; all time, capacity and utilisation metrics are currently unsupported/null for both PMS providers.
- 400 / 415 / 422
- Correct the request/schema/media type; do not retry unchanged invalid or prohibited content.
- 401 / 403
- Reconnect or stop. Never silently request broader scopes.
- 404
- Unavailable or concealed resource; do not infer existence.
- 409 / 412 / 428
- Resolve operation/source/state conflicts or reload the current ETag before an explicit retry.
- 413 / 429
- Reduce input or wait for the current budget reset. Respect Retry-After; changing grants/transports does not reset shared budgets.
- 503
- Bounded temporary failure. For an uncertain write, retain the exact UUID/body and inspect status before retrying.
Errors contain code, a fixed message, retryable and request_id. Share the request ID with support; do not send tokens, prompts, source bodies or logs containing practice content.
Provider compatibility
| Provider | Current status |
|---|---|
| ChatGPT | Documented MCP plugin route; live CliniBase canary pending |
| Claude | Documented custom remote MCP connector route; live canary pending |
| ChatGPT Dots | Installed plugin candidate; separate background validation pending |
| Gemini, Copilot, Grok, Perplexity | Research candidates; no compatibility claim |
Plan, workspace/admin permissions, client registration and provider confirmation behaviour vary. A ChatGPT chat test does not prove Dots background access. Cloud Dots work can continue with devices off; local-computer work requires the connected computer online and its app open.
OpenAI authentication · Claude connector guide · Dots environment guide
Sandbox
A hosted preview sandbox is not publicly available yet. Preview onboarding uses an isolated fictional practice, separate app grants and normal quotas. Never copy production records, patient data or sensitive HR material into a sandbox.
Repository contributors can run scripts/external_api_sandbox with an isolated test database named clinibase_external_sandbox_…. It refuses production environments and other database names. The authored blue-cabinet fixtures exercise the same consent, source selection, proposal receipt, review and revocation boundaries. Local mock mode stays development-only.
Walkthrough: owner allows a fictional app → member selects a source and scopes → developer reads that source → app returns a guide proposal → another person checks and accepts → independent reviewer approves → member disconnects → subsequent tool call is denied. Record confusion and useful outcomes during the human exercise; automated tests alone do not complete it.
Privacy and security
Only selected, safe, current operational projections leave CliniBase. Connecting sends that information to the chosen provider. Non-clinical information can still be confidential: review your provider’s retention, training, processing-location and other connected-tool settings before enabling access.
Revocation stops new admission, not information already delivered. Text screening is defence in depth, not a guarantee that sensitive material can never be entered. People remain responsible for source selection and draft review. No absolute security, anonymity, residency or compliance certification is claimed.
Never paste credentials into an AI conversation, put them in URLs, or forward a CliniBase bearer to another audience. External content and reference URLs cannot grant authority or bypass independent approval.
Versioning and support
Version 0.1.0-preview, 3 October 2026. First local preview: OAuth consent/revocation, selected reads, immutable contributions, native human intake and MCP parity. Provider availability and release approvals remain pending.
V1 is the business resource version. Preview contracts may change with an explicit changelog; clients must tolerate new documented optional fields, treat unknown states as unsupported and never broaden actions automatically. Breaking released changes require a new version and documented migration/deprecation dates. No public availability or support SLA is promised for this preview.
Developer support and security reports. Include a request ID, app/version and fictional reproduction; omit secrets and practice content.