For AI agents: a documentation index is available at /llms.txt. A markdown version of this page is available at /guide/api/authentication.md.
Authentication
Send your API key in the X-API-KEY header and encrypt the payload with AES-256-CBC.
The Developer API uses an API key plus an encrypted payload. The key, sent
in the X-API-KEY header, identifies you. The secret is never sent: it is the
key that encrypts your request body, so only someone holding it can produce a
payload the server will accept.
Keep the secret server side
The secret decrypts your payloads. Keep it in a secrets manager, never in source control, a mobile app or browser code. If it leaks, rotate the key pair from the admin portal.
#Credentials
| Value | Where it goes | Notes |
|---|---|---|
| API key | X-API-KEY header | Public identifier, prefixed osk_ |
| Secret key | never transmitted | 64 characters, used only to encrypt the payload |
A key is bound to one company and one user. Every call runs as that user, so you never pass a company or user id to identify yourself.
#Required headers
Every request carries three headers:
Acceptstringrequiredapplication/json
Content-Typestringrequiredapplication/json
X-API-KEYstringrequiredYour API key, starting with osk_. It must be active and not past its expiry
date.
#Encrypting the payload
The API key is what authenticates a request. Its parameters, though, are read only from the encrypted payload: parameters sent as plain JSON are ignored, not rejected, so a list call silently returns unfiltered results and a create fails validation. Encrypt them and wrap them in a single field:
{
"encrypted_data": "<base64 string>"
}
The encryption is AES-256-CBC:
- Key: the SHA-256 hash of your secret key (32 raw bytes)
- IV: 16 random bytes, generated fresh for every request
- Output: the IV prepended to the ciphertext, then Base64-encoded
Responses come back as ordinary JSON, with one exception: the timelog
endpoints return their response encrypted the same way, as
{"encrypted_data": "<base64 string>"}. Decrypt it with the same key: Base64
decode, take the first 16 bytes as the IV, and AES-256-CBC decrypt the rest to
get the usual success / message / data envelope.
#Worked example
<?php
$apiKey = 'osk_your_api_key';
$secretKey = 'your_secret_key';
$path = '/api/v1/partner/tasks/list';
$payload = ['project_id' => '550e8400-e29b-41d4-a716-446655440000'];
// 1. Encrypt the payload with AES-256-CBC.
$iv = random_bytes(16);
$cipher = openssl_encrypt(
json_encode($payload),
'aes-256-cbc',
hash('sha256', $secretKey, true),
OPENSSL_RAW_DATA,
$iv
);
$body = json_encode(['encrypted_data' => base64_encode($iv . $cipher)]);
// 2. Send it.
$ch = curl_init("https://v4-api.orangescrum.com{$path}");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: application/json',
"X-API-KEY: {$apiKey}",
],
]);
echo curl_exec($ch);
#Why a request fails
401 โ API key is required
The X-API-KEY header is missing. Check that your HTTP client is not
dropping custom headers, for example on a redirect.
401 โ Invalid API key
The key does not exist, is inactive, or has passed its expiry date. Check it in the admin portal.
401 โ Failed to decrypt request data
The key was accepted but encrypted_data did not decrypt with its secret.
Usually the wrong secret for this key, a key derived from the secret as
text rather than its raw SHA-256 bytes, a missing IV prefix, or Base64 that
was altered in transit.
Every request, successful or not, is written to the audit trail with the calling IP, the endpoint and the response status.
๐Validate your setupCall /api/v1/partner/validate first. It decrypts your payload and echoes it
back without touching any data.