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>
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.
Recommended Client Integration Pattern
Authenticate user in your app.
Load API key from secure backend for that user context.
Prompt for user secret when generating keys, rekeying, decrypting values, or handling encrypted file content.
Cache minimal session state and clear on logout.
Use org user discovery plus share routes for controlled peer-to-peer secret and file access.
Use recovery routes for trusted-user restore workflows.
Retry idempotent operations with exponential backoff.