Authentication
The Patholytix API uses OAuth 2.0 Client Credentials flow. This is a machine-to-machine authentication pattern — your integration authenticates as itself, not as a user.
How it works
Your Application
│
│ ① POST /oauth/token (client_id + client_secret)
▼
Authorization Server ──► Issues signed JWT (TTL: 1–12 hours)
│
│ ② API Request + Authorization: Bearer <JWT>
▼
Patholytix API ──► Validates token, checks scopes, returns response
Requesting credentials
Contact your Deciphex account representative to request a set of integration credentials. Your representative will provision a client and share your credentials securely via Bitwarden — you will receive a Bitwarden Send link to retrieve them.
Your credentials consist of:
client_id— your client identifierclient_secret— your client secret (treat as a password — never expose in client-side code, logs, or version control)
Your client will be configured with:
- An organisation that determines which Patholytix instance you have access to
- One or more sites within that organisation that your client can read and write data for
- One or more role bundles that determine which API operations you can perform (see scopes below)
Role bundles and scopes
| Role Bundle | Scopes | Description |
|---|---|---|
Study Management | STUDY_READ, STUDY_WRITE, COHORT_READ, COHORT_WRITE, METADATA_READ, METADATA_WRITE, IMAGE_READ | Full read/write on studies, cohorts, metadata, images |
Study Reporting | STUDY_READ, SCORE_READ, ANNOTATION_READ, REPORT_READ, REPORT_EXPORT | Read access to studies, scores, and exports |
Image Ingestion | IMAGE_WRITE, STUDY_READ, COHORT_READ | Write images, read studies and cohorts |
Your JWT token will contain the scopes for the role bundles assigned to your client. Calling an endpoint that requires a scope you do not have results in 403 Forbidden.
Getting a token
Exchange your credentials for a JWT access token:
curl -X POST https://api.account.dev.patholytix.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
| Field | Description |
|---|---|
access_token | The JWT to include on API requests |
token_type | Always Bearer |
expires_in | Seconds until the token expires (configured per client: 1–12 hours) |
Using the token
Include the access token in the Authorization header on every API request:
curl https://api.dev.patholytix.com/api/v1/studies \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Token expiry and refresh
Tokens do not support refresh. When a token expires, request a new one using the same POST /oauth/token request. Tokens are valid for between 1 and 12 hours — the TTL is set per client at registration time.
Best practice: Request a new token proactively before the current one expires. Check the expires_in field and refresh when ~5 minutes remain.
Python example
import requests
import time
TOKEN_URL = "https://api.account.dev.patholytix.com/oauth/token"
API_BASE = "https://api.dev.patholytix.com/api"
def get_token(client_id: str, client_secret: str) -> dict:
resp = requests.post(TOKEN_URL, data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
})
resp.raise_for_status()
return resp.json()
token_response = get_token("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
access_token = token_response["access_token"]
headers = {"Authorization": f"Bearer {access_token}"}
resp = requests.get(f"{API_BASE}/v1/studies", headers=headers)
print(resp.json())
Error responses
| Status | Meaning |
|---|---|
401 Unauthorized | Token is missing, malformed, expired, or has an invalid signature |
403 Forbidden | Token is valid but lacks the required scope for the requested operation |
See Error Codes for the full error response format.