Skip to content

Authentication

B2B integrations authenticate using OAuth 2.0 Client Credentials flow, the industry standard for server-to-server communication.

Contact your Crinsutrack administrator to register your integration. You will receive:

  • Client ID - Public identifier for your application
  • Client Secret - Keep this secure, never expose in client-side code

Exchange your client credentials for an access token:

Terminal window
curl -X POST https://api.crinsutrack.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
}
FieldDescription
access_tokenJWT access token for API requests
token_typeAlways Bearer
expires_inToken lifetime in seconds (1 hour)

Include the token in the Authorization header:

Terminal window
curl https://api.crinsutrack.com/api/subjects \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

When the access token expires, request a new one using your client credentials. There is no refresh token in this flow.

Terminal window
curl -X POST https://api.crinsutrack.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"

import requests
from datetime import datetime, timedelta
class CrinsutrackClient:
def __init__(self, client_id, client_secret):
self.base_url = "https://api.crinsutrack.com"
self.client_id = client_id
self.client_secret = client_secret
self.token = None
self.token_expires = None
def authenticate(self):
"""Request access token using client credentials."""
response = requests.post(
f"{self.base_url}/oauth/token",
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
},
headers={"Content-Type": "application/x-www-form-urlencoded"}
)
response.raise_for_status()
data = response.json()
self.token = data["access_token"]
self.token_expires = datetime.now() + timedelta(seconds=data["expires_in"] - 60)
def get_token(self):
"""Get valid access token, re-authenticating if needed."""
if not self.token or self.token_expires <= datetime.now():
self.authenticate()
return self.token
def request(self, method, endpoint, **kwargs):
headers = kwargs.pop("headers", {})
headers["Authorization"] = f"Bearer {self.get_token()}"
return requests.request(
method,
f"{self.base_url}{endpoint}",
headers=headers,
**kwargs
)
# Usage
client = CrinsutrackClient(
client_id="your-client-id",
client_secret="your-client-secret"
)
# Create a procedure request
request = client.request("POST", "/api/procedure-requests", json={
"type": "STORE",
"facilityId": 1,
"subjectCode": "SUBJ-2024-001",
"sampleContainers": [
{"sampleContainerId": "VL-001", "description": "Vial 1"}
]
}).json()
# Check request status
status = client.request("GET", f"/api/procedure-requests/{request['id']}").json()

  1. Never expose client secrets in frontend code, mobile apps, or public repositories
  2. Store secrets securely using environment variables, AWS Secrets Manager, or HashiCorp Vault
  3. Use HTTPS for all API requests
  4. Rotate secrets regularly - contact your administrator to generate new credentials
  5. Use separate credentials for development, staging, and production environments
  6. Monitor API usage - review access logs for unexpected activity

To revoke your integration’s access:

  1. Contact your Crinsutrack administrator
  2. The client credentials will be invalidated
  3. All active tokens will be rejected

ErrorCauseSolution
401 invalid_clientInvalid client ID or secretVerify OAuth credentials
401 UnauthorizedInvalid or expired tokenRequest a new token
403 ForbiddenOperation not permitted for B2BSee Roles & Permissions
429 Too Many RequestsRate limit exceededWait and retry with backoff