For AI agents: a documentation index is available at /llms.txt. A markdown version of this page is available at /guide/api/authentication.md.

APIIntroduction

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

ValueWhere it goesNotes
API keyX-API-KEY headerPublic identifier, prefixed osk_
Secret keynever transmitted64 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:

Acceptstringrequired

application/json

Content-Typestringrequired

application/json

X-API-KEYstringrequired

Your 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:

json
{
  "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
<?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 setup

Call /api/v1/partner/validate first. It decrypts your payload and echoes it back without touching any data.