Proxy Developer Guide

Proxy Developer Guide (End Developers)

This guide documents the implemented Proxy service in harden-v2/src/Proxy.

Use Proxy when you want Backend-backed storage without sending plaintext secrets or plaintext file payloads to the Backend service.

Why Use Proxy API

Use this API when your app must store secrets or files but you do not want plaintext data persisted on the backend.

Benefits:

  • Client-side style protection with server-side API simplicity.
  • Backend stores encrypted payloads and wrapped keys, not plaintext values.
  • User-scoped access with API keys and user-secret based unwrap where decryption is required.
  • Built-in key generation, rekeying, encrypted KV, encrypted files, zero-knowledge sharing, user discovery, and trusted-user recovery routes.
  • Admin passthrough routes for org and user management.

Implemented Feature Set

  • User key generation and key-status inspection.
  • User-secret rekey without changing the API key.
  • Encrypted key/value write, read, list, and delete.
  • Read-only KV sharing with encrypted recipient aliases, optional expiry, owner revoke, and recipient self-removal.
  • Encrypted file upload, list, download, and delete.
  • Read-only file sharing with optional expiry, owner revoke, and recipient self-removal.
  • Org-scoped user discovery for trusted-user workflows.
  • Trusted-user recovery share setup, listing, removal, inspection, and restore submission.
  • Pass-through admin routes for org creation, user management, and API key rotation.

Base Endpoint

  • Dev endpoint: https://dev2-api.harden.cloud
  • Required for all authenticated routes: X-Api-Key: <user_api_key>

Required Headers

Header Required For Notes
X-Api-Key all authenticated proxy routes identifies the current user
X-User-Secret key generation, rekey input, KV reads, KV share creation and recipient reads, file upload/list/download, file share creation and recipient reads used to decrypt encrypted private keys or derive a KEK
X-New-User-Secret POST /v1/keys/rekey the replacement user secret

API Reference And Examples

Core Workflows

1. Check API health

1
curl -sS https://dev2-api.harden.cloud/health
1
2
const healthRes = await fetch("https://dev2-api.harden.cloud/health");
console.log(await healthRes.json());

2. Check whether the user already has encryption keys

1
2
3
curl -sS \
  -H "X-Api-Key: ${API_KEY}" \
  https://dev2-api.harden.cloud/v1/keys/status
1
2
3
4
5
const statusRes = await fetch("https://dev2-api.harden.cloud/v1/keys/status", {
  headers: { "X-Api-Key": apiKey }
});
const keyStatus = await statusRes.json();
console.log(keyStatus);

Response:

1
{ "hasEcc": true, "hasKyber": true }

3. Generate keys for a user

1
2
3
4
curl -sS -X POST \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  https://dev2-api.harden.cloud/v1/keys/generate
1
2
3
4
5
6
7
await fetch("https://dev2-api.harden.cloud/v1/keys/generate", {
  method: "POST",
  headers: {
    "X-Api-Key": apiKey,
    "X-User-Secret": userSecret
  }
});

Response includes generated public keys:

1
2
3
4
{
  "publicKeyB64": "<ecc-public-key>",
  "publicKeyKyberB64": "<kyber-public-key>"
}

4. Rekey the user’s encrypted private keys

1
2
3
4
5
curl -sS -X POST \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${OLD_USER_SECRET}" \
  -H "X-New-User-Secret: ${NEW_USER_SECRET}" \
  https://dev2-api.harden.cloud/v1/keys/rekey

Use this when a user changes the secret that protects their encrypted private keys. Existing KV and file data remains readable after rekey.

5. Save a value (encrypted by Proxy)

1
2
3
4
5
curl -sS -X PUT \
  -H "X-Api-Key: ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"valueB64":"aHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20="}' \
  "https://dev2-api.harden.cloud/v1/kv/app%2Fconfig%2Fbase-url"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const plaintext = "https://api.example.com";
const valueB64 = btoa(plaintext);

await fetch("https://dev2-api.harden.cloud/v1/kv/app%2Fconfig%2Fbase-url", {
  method: "PUT",
  headers: {
    "X-Api-Key": apiKey,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ valueB64 })
});

6. List stored keys

1
2
3
curl -sS \
  -H "X-Api-Key: ${API_KEY}" \
  https://dev2-api.harden.cloud/v1/kv
1
2
3
4
const keysRes = await fetch("https://dev2-api.harden.cloud/v1/kv", {
  headers: { "X-Api-Key": apiKey }
});
const keys = await keysRes.json();

7. Read and decrypt one value

1
2
3
4
curl -sS \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  "https://dev2-api.harden.cloud/v1/kv/app%2Fconfig%2Fbase-url"
1
2
3
4
5
6
7
8
9
const readRes = await fetch("https://dev2-api.harden.cloud/v1/kv/app%2Fconfig%2Fbase-url", {
  headers: {
    "X-Api-Key": apiKey,
    "X-User-Secret": userSecret
  }
});
const readPayload = await readRes.json();
const value = atob(readPayload.valueB64);
console.log(value);

8. Delete a value

1
2
3
curl -sS -X DELETE \
  -H "X-Api-Key: ${API_KEY}" \
  "https://dev2-api.harden.cloud/v1/kv/app%2Fconfig%2Fbase-url"
1
2
3
4
await fetch("https://dev2-api.harden.cloud/v1/kv/app%2Fconfig%2Fbase-url", {
  method: "DELETE",
  headers: { "X-Api-Key": apiKey }
});

File API

Files use the same user API key and encryption model.

9. Upload an encrypted file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -sS -X POST \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName":"credentials.json",
    "contentB64":"ewogICJ0b2tlbiI6ICJhYmMiCn0=",
    "contentType":"application/json"
  }' \
  https://dev2-api.harden.cloud/v1/files
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const bytes = new TextEncoder().encode(JSON.stringify({ token: "abc" }));
const contentB64 = btoa(String.fromCharCode(...bytes));

await fetch("https://dev2-api.harden.cloud/v1/files", {
  method: "POST",
  headers: {
    "X-Api-Key": apiKey,
    "X-User-Secret": userSecret,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    fileName: "credentials.json",
    contentB64,
    contentType: "application/json"
  })
});

10. List files visible to the user

1
2
3
4
curl -sS \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  "https://dev2-api.harden.cloud/v1/files"
1
2
3
4
5
6
7
const filesRes = await fetch("https://dev2-api.harden.cloud/v1/files", {
  headers: {
    "X-Api-Key": apiKey,
    "X-User-Secret": userSecret
  }
});
const files = await filesRes.json();

11. Download and decrypt one file

1
2
3
4
curl -sS \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  "https://dev2-api.harden.cloud/v1/files/${FILE_ID}"
1
2
3
4
5
6
7
const downloadRes = await fetch(`https://dev2-api.harden.cloud/v1/files/${fileId}`, {
  headers: {
    "X-Api-Key": apiKey,
    "X-User-Secret": userSecret
  }
});
const filePayload = await downloadRes.json();

12. Delete one file

1
2
3
curl -sS -X DELETE \
  -H "X-Api-Key: ${API_KEY}" \
  https://dev2-api.harden.cloud/v1/files/${FILE_ID}
1
2
3
4
await fetch(`https://dev2-api.harden.cloud/v1/files/${fileId}`, {
  method: "DELETE",
  headers: { "X-Api-Key": apiKey }
});

13. Share a value with another user in the same org

Shares are currently read-only. The owner unwraps the stored DEK locally in Proxy, re-wraps it to the recipient’s public keys, and Backend still never receives plaintext.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl -sS -X POST \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  -H "Content-Type: application/json" \
  -d '{
    "recipientUserId":"00000000-0000-0000-0000-000000000000",
    "permission":"read",
    "alias":"database password",
    "expiresUtc":"2026-03-24T12:00:00Z"
  }' \
  "https://dev2-api.harden.cloud/v1/kv/app%2Fconfig%2Fbase-url/shares"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
await fetch("https://dev2-api.harden.cloud/v1/kv/app%2Fconfig%2Fbase-url/shares", {
  method: "POST",
  headers: {
    "X-Api-Key": apiKey,
    "X-User-Secret": userSecret,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    recipientUserId,
    permission: "read",
    alias: "database password",
    expiresUtc: "2026-03-24T12:00:00Z"
  })
});

14. List secrets shared with the current user

1
2
3
4
curl -sS \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  https://dev2-api.harden.cloud/v1/kv/shared

Use this recipient inbox to show owner names, encrypted aliases, and any expiry before fetching the shared plaintext from GET /v1/kv/shared/{shareId}.

15. Share an encrypted file with another user

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -sS -X POST \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  -H "Content-Type: application/json" \
  -d '{
    "recipientUserId":"00000000-0000-0000-0000-000000000000",
    "permission":"read",
    "expiresUtc":"2026-03-24T12:00:00Z"
  }' \
  "https://dev2-api.harden.cloud/v1/files/${FILE_ID}/shares"

16. Download a file shared with the current user

1
2
3
4
curl -sS \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-User-Secret: ${USER_SECRET}" \
  "https://dev2-api.harden.cloud/v1/files/shared/${SHARE_ID}"

The recipient can remove their own access with DELETE /v1/files/shared/{shareId}. The owner can revoke access by deleting /v1/files/{id}/shares/{recipientUserId}.

17. Discover users in the same org

1
2
3
curl -sS \
  -H "X-Api-Key: ${API_KEY}" \
  https://dev2-api.harden.cloud/v1/users/org

Use this to populate trusted-user selectors or org-scoped user directories.

18. Manage trusted-user recovery shares

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -sS -X PUT \
  -H "X-Api-Key: ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "encryptedPrivateKeyEccShareB64":"YmFzZTY0LWVjYy1zaGFyZQ==",
    "encryptedPrivateKeyKyberShareB64":"YmFzZTY0LWt5YmVyLXNoYXJl",
    "xorApplied":true,
    "xorSaltHint":"dog-name"
  }' \
  "https://dev2-api.harden.cloud/v1/recovery/trustees/${TRUSTEE_USER_ID}"

Companion routes:

  • GET /v1/recovery/trustees
  • DELETE /v1/recovery/trustees/{trusteeUserId}
  • GET /v1/recovery/shares
  • GET /v1/recovery/shares/{ownerUserId}
  • POST /v1/recovery/restore/{ownerUserId}

19. Use admin passthrough routes when needed

The Proxy service also forwards a subset of Backend admin routes unchanged:

  • POST /admin/orgs
  • GET /admin/orgs
  • POST /admin/orgs/{orgId}/users
  • GET /admin/orgs/{orgId}/users
  • POST /admin/orgs/{orgId}/users/{userId}/apikey/rotate

Use these only when you want to go through the Proxy host. The canonical admin documentation is in the Backend section.

Error Handling

  • 401 or 403: invalid API key, wrong user secret, expired session, or share access that no longer matches the user’s current keys.
  • 400: invalid payload, missing user secret, crypto validation failure, or plan/storage validation failure.
  • 404: key, file, user, or share not found.
  • 409: active share already exists for that recipient and resource.
  • 429: apply client retry/backoff.
  • 5xx: transient server issue; use idempotent retries.

Security Guidance

  • Never log plaintext secrets, userSecret, or raw payloads.
  • Keep API keys in a secrets manager; rotate if leaked.
  • Use TLS-only transport and pin hostnames in production clients.
  • Store user secret only in secure session memory, not long-lived storage.
  1. Authenticate user in your app.
  2. Load API key from secure backend for that user context.
  3. Prompt for user secret when generating keys, rekeying, decrypting values, or handling encrypted file content.
  4. Cache minimal session state and clear on logout.
  5. Use org user discovery plus share routes for controlled peer-to-peer secret and file access.
  6. Use recovery routes for trusted-user restore workflows.
  7. Retry idempotent operations with exponential backoff.