API Reference

SentraShare REST API

Programmatically create, retrieve and manage zero-knowledge secrets. All encryption happens client-side - the server only stores ciphertext it cannot read.

Base URL https://sentrashare.net
Version v1
Format JSON
Plan Enterprise only
Authentication
🔑
All API requests require a Bearer token in the Authorization header.
Generate your API key via POST /api/auth/api-key after logging in. API keys start with sk_ and are shown only once - store them securely.

Include your key in every request:

http
Authorization: Bearer sk_your_api_key_here

Zero-Knowledge note: SentraShare never sees your plaintext. Before calling POST /api/secrets, encrypt your payload client-side using XSalsa20-Poly1305 (libsodium / TweetNaCl). Pass the Base64-encoded ciphertext and nonce. The encryption key lives only in the share URL fragment (#key=…) and is never sent to the server.

Quick Start (PHP Example)

The most common use-case: your application creates a secret via the API and sends the resulting share URL to a recipient. The recipient opens the URL in any browser - the page decrypts the secret client-side without the server ever seeing the plaintext.

Prerequisites: PHP 8.1+, ext-sodium (bundled since PHP 7.2), and a valid API key (sk_…).

Step 1 Generate a random encryption key and encrypt the plaintext client-side (your PHP server)
php
<?php
// composer require ext-sodium  (bundled in PHP 7.2+, nothing to install)

$plaintext  = 'My super secret message';          // what you want to protect
$key        = sodium_crypto_secretbox_keygen();    // 32 random bytes
$nonce      = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 random bytes
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
Step 2 POST the Base64-encoded ciphertext and nonce to the API
php
$apiKey  = 'sk_your_api_key_here';
$scheme  = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$baseUrl = $scheme . '://' . $_SERVER['HTTP_HOST'];

$payload = json_encode([
    'ciphertext' => base64_encode($ciphertext),
    'nonce'      => base64_encode($nonce),
    'ttl_hours'  => 24,    // secret expires after 24 hours
    'max_views'  => 1,     // deleted after first view
]);

$ch = curl_init($baseUrl . '/api/secrets');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $apiKey,
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

// $response['id'] contains the secret UUID
$secretId = $response['id'];
Step 3 Build the share URL - the key goes in the URL fragment (#), which is never sent to the server
php
// The fragment (#key=...) is processed by the browser only -
// it is never transmitted to the server. This is what keeps
// the encryption truly zero-knowledge.
$shareUrl = $baseUrl . '/s/' . $secretId . '#key=' . base64_encode($key);

// Send $shareUrl to the recipient, e.g. via email:
// "Click here to view your secret: {$shareUrl}"
echo $shareUrl;
Step 4 Recipient opens the URL in any browser - no account needed
🔒
The browser fetches only the ciphertext from the server, then decrypts it locally using the key from the URL fragment. The server never sees the key or the plaintext. Once viewed, the secret is permanently deleted (with max_views: 1).
Note on GET /api/secrets/:id
This endpoint returns the raw ciphertext - not the plaintext. It is only useful if your application already holds the encryption key and can decrypt client-side. For the typical flow described above (create via API → recipient views in browser), you do not need this endpoint.
With Passphrase Protection

A passphrase adds a second factor: the recipient must enter it in the browser before the secret is revealed. The server never stores the passphrase itself - only a bcrypt hash of HMAC-SHA256(passphrase, encryptionKey). This means the passphrase cannot be brute-forced without also knowing the key.

In PHP you can compute the HMAC with the built-in hash_hmac() function and encode the raw binary result as Base64.

php
<?php
$plaintext  = 'My super secret message';
$passphrase = 'correct horse battery staple';   // share this separately with the recipient

$key   = sodium_crypto_secretbox_keygen();      // 32 random bytes
$nonce = random_bytes(12);                       // 12 bytes for AES-GCM

// Encrypt with AES-256-GCM
$ciphertext = openssl_encrypt(
    $plaintext,
    'aes-256-gcm',
    $key,
    OPENSSL_RAW_DATA,
    $nonce,
    $tag                          // 16-byte auth tag, appended to ciphertext
);
$ciphertextWithTag = $ciphertext . $tag;

// Build passphrase verifier: HMAC-SHA256(passphrase, key) - raw binary, then base64
$verifier = base64_encode(hash_hmac('sha256', $passphrase, $key, true));

$scheme  = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$baseUrl = $scheme . '://' . $_SERVER['HTTP_HOST'];

// POST to API
$payload = json_encode([
    'ciphertext'          => base64_encode($ciphertextWithTag),
    'nonce'               => base64_encode($nonce),
    'ttl_hours'           => 24,
    'max_views'           => 1,
    'passphrase_verifier' => $verifier,
]);

$ch = curl_init($baseUrl . '/api/secrets');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Authorization: Bearer sk_your_api_key_here',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

// Build share URL - key in URL fragment, passphrase sent separately (e.g. by phone/SMS)
$keyB64Url = rtrim(strtr(base64_encode($key), '+/', '-_'), '=');
$shareUrl  = $baseUrl . '/s/' . $response['id'] . '#key=' . $keyB64Url;

// Send $shareUrl to recipient via email
// Send $passphrase to recipient via a DIFFERENT channel (SMS, phone call, etc.)
echo "URL: {$shareUrl}
";
echo "Passphrase (send separately!): {$passphrase}
";
⚠️
Never send the URL and passphrase through the same channel. If an attacker intercepts the email, they should still need the passphrase from a second channel (SMS, phone call) to access the secret.
File Upload Enterprise

Files are encrypted as a single chunk (the entire file content). Before encryption, a small JSON metadata header ({"name":"…","size":…,"type":"…"}) is prepended so the browser can reconstruct the filename and MIME type when the recipient downloads.

The flow: create a secret with is_file: true → upload the encrypted chunk → build the share URL. The recipient's browser downloads the chunk, decrypts it, strips the metadata header, and offers a download.

php
<?php
$filePath   = '/path/to/document.pdf';
$fileName   = basename($filePath);
$fileBytes  = file_get_contents($filePath);
$mimeType   = mime_content_type($filePath) ?: 'application/octet-stream';
$passphrase = 'optional-passphrase';           // set to '' or null to skip

$key   = sodium_crypto_secretbox_keygen();     // 32 random bytes
$nonce = random_bytes(12);                      // 12 bytes for AES-GCM

// ── Step 1: Build payload (metadata header + raw file bytes) ─────────────────
// The browser uses this header to restore the filename and offer a download.
$meta     = json_encode(['name' => $fileName, 'size' => strlen($fileBytes), 'type' => $mimeType]);
$combined = $meta . "" . $fileBytes;        // null byte separates header from content

// ── Step 2: Encrypt with AES-256-GCM ─────────────────────────────────────────
$ciphertext = openssl_encrypt(
    $combined,
    'aes-256-gcm',
    $key,
    OPENSSL_RAW_DATA,
    $nonce,
    $tag
);
$ciphertextWithTag = $ciphertext . $tag;        // tag is always 16 bytes

// ── Step 3: Build passphrase verifier (optional) ─────────────────────────────
$verifier = $passphrase
    ? base64_encode(hash_hmac('sha256', $passphrase, $key, true))
    : null;

// ── Step 4: Create secret record with is_file = true ─────────────────────────
$apiKey  = 'sk_your_api_key_here';
$scheme  = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$baseUrl = $scheme . '://' . $_SERVER['HTTP_HOST'];

$secretPayload = array_filter([
    'ciphertext'          => base64_encode($ciphertextWithTag),
    'nonce'               => base64_encode($nonce),
    'ttl_hours'           => 48,
    'max_views'           => 1,
    'is_file'             => true,
    'passphrase_verifier' => $verifier,
]);

$ch = curl_init($baseUrl . '/api/secrets');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($secretPayload),
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $apiKey,
    ],
]);
$secretData = json_decode(curl_exec($ch), true);
curl_close($ch);

$secretId = $secretData['id'];

// ── Step 5: Upload the encrypted chunk (chunk index 0) ────────────────────────
// For files larger than your memory limit, split into chunks and upload each
// with an incrementing chunk_index (0, 1, 2, …).
$ch = curl_init("{$baseUrl}/api/files/{$secretId}/0");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode(['ciphertext' => base64_encode($ciphertextWithTag)]),
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $apiKey,
    ],
]);
$chunkResult = json_decode(curl_exec($ch), true);
curl_close($ch);

if (empty($chunkResult['stored'])) {
    throw new RuntimeException('Chunk upload failed: ' . json_encode($chunkResult));
}

// ── Step 6: Build share URL ───────────────────────────────────────────────────
$keyB64Url = rtrim(strtr(base64_encode($key), '+/', '-_'), '=');
$shareUrl  = "{$baseUrl}/s/{$secretId}#key={$keyB64Url}";

echo "Share URL: {$shareUrl}
";
if ($passphrase) {
    echo "Passphrase (send via separate channel): {$passphrase}
";
}
Large files: The Enterprise plan allows up to 8 MB per file.
Errors & Rate Limits

All errors return a JSON object {"error": "message"} with an appropriate HTTP status code.

Rate limits are enforced per tenant per hour. The current limit for Enterprise is 500 creates/hour and 500 reads/minute. Check remaining quota with GET /api/rate-status. Exceeded limits return 429 Too Many Requests.

http status codes
200  OK
201  Created
400  Bad Request       - missing or malformed parameter
401  Unauthorized      - missing or invalid API key
403  Forbidden         - plan restriction or invalid token
404  Not Found         - secret consumed, expired, or not found
422  Unprocessable     - validation error
429  Too Many Requests - rate limit exceeded
Secrets
POST /api/secrets Create a secret

Creates a new zero-knowledge secret. Encrypt your payload client-side before calling this endpoint.

ParameterTypeDescription
ciphertextrequiredstringBase64-encoded encrypted payload (XSalsa20-Poly1305)
noncerequiredstringBase64-encoded 24-byte nonce used during encryption
ttl_hoursoptionalintegerTime-to-live in hours. Default: 24. Max: 720 (enterprise)
max_viewsoptionalintegerMaximum number of views before deletion. Default: 1. Max: 100
passphrase_verifieroptionalstringHMAC-based passphrase verifier. Server stores bcrypt hash only.
is_fileoptionalbooleanSet true if secret references encrypted file chunks
policyoptionalobjectAdditional policy metadata (JSON object)
request
POST /api/secrets
Authorization: Bearer sk_…
Content-Type: application/json

{
  "ciphertext": "base64encodedCiphertext==",
  "nonce":      "base64encodedNonce==",
  "ttl_hours":  48,
  "max_views":  1
}
response 201
{
  "id":           "uuid-of-secret",
  "delete_token": "hex-token-for-deletion",
  "expires_at":   "2026-03-11T12:00:00+00:00"
}
201Secret created. Store the delete_token to revoke it later.
422Validation error - missing ciphertext/nonce or limit exceeded.
429Rate limit exceeded.
GET /api/secrets/:id Retrieve & consume a secret

Atomically fetches and consumes a secret. Once all views are consumed the secret is permanently deleted. For passphrase-protected secrets use POST /api/secrets/:id with the verifier in the body.

ParameterTypeDescription
idrequiredstring (path)Secret UUID
passphrase_verifieroptionalstring (query)Required if secret is passphrase-protected
response 200
{
  "ciphertext": "base64encodedCiphertext==",
  "nonce":      "base64encodedNonce==",
  "is_file":    false,
  "policy":     {}
}
200Returns ciphertext + nonce. Decrypt client-side with the key from the URL fragment.
403Invalid passphrase.
404Secret not found, expired, or already consumed.
DELETE /api/secrets/:id Delete a secret

Permanently deletes a secret before it expires or is consumed. Requires the delete_token returned at creation time.

ParameterTypeDescription
delete_tokenrequiredstring (body)Token returned by POST /api/secrets
200{"deleted": true}
404Secret not found or invalid token.
GET /api/rate-status Check rate limit quota

Returns the current rate limit status for your tenant.

response 200
{
  "limit":     500,
  "used":      12,
  "remaining": 488,
  "reset_at":  1741518000,
  "plan":      "enterprise"
}
Files Enterprise
POST /api/files/:secretId/:chunkIndex Upload encrypted file chunk

Uploads a single encrypted chunk of a file. Split large files into chunks, encrypt each client-side, upload sequentially starting from index 0. The secret must be created first with is_file: true.

ParameterTypeDescription
ciphertextrequiredstringBase64-encoded encrypted chunk data
201{"stored": true}
403File upload not available on your plan.
GET /api/files/:secretId/:chunkIndex Download encrypted file chunk
200{"ciphertext": "base64…"} - decrypt client-side
404Chunk not found.
Auth
POST /api/auth/api-key Generate or revoke API key

Requires an active browser session (logged in as Enterprise user). Generates a new sk_… API key - shown only once. Calling again rotates the key.

ParameterTypeDescription
actionoptionalstringgenerate (default) or revoke
response 201
{
  "api_key": "sk_4a7f1b3c…",
  "note":    "Store this key securely - it will not be shown again."
}
201Key generated. Store immediately.
403API access not available on your plan.

Ready to use the API?

API access is available on the Enterprise plan. Upgrade to get full programmatic access, higher rate limits, and Swagger UI for interactive testing.

View Enterprise Plan Login