Get started with ZedGi â
Connect any TCP database to the edge in minutes. No open ports. No VPNs. Just HTTPS.
1. Create an account and service â
Sign in at zedgi.app using GitHub or Google. Once inside the dashboard, click New Service.
Choose your database type (Redis, Postgres, or MySQL), paste your connection string, and optionally restrict access to specific IPs.
2. Generate an API key â
After the service is created, go to the service detail page and click Create API Key.
Zedgi uses a two-value key model:
| Value | Header | Purpose |
|---|---|---|
key | x-zedgi-key | Public key identifier (starts zk_) |
signing_secret | (used to sign) | HMAC-SHA256 secret for request auth |
Both values are shown only once at creation â store them securely. The key is hashed with SHA-256 before storage and cannot be recovered. The signing_secret is encrypted with AES-256-GCM on the server.
# Example: what the API key endpoint returns
{
"id": "a1b2c3d4e5f6...",
"key": "zk_live_1a2b3c4d5e6f7890abcdef1234567890abcdef12",
"signing_secret": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"label": "production",
"message": "Save both values â they will not be shown again."
}3. Install the client â
npm install @zedgi/zedgi-client4. Authenticating requests â
Every Zedgi RPC request requires the API key identifier plus signing headers. When you also supply a credential, the x-zedgi-cred "link" (encrypted credentials) is added automatically by the client. The x-zedgi-key identifies you; the ts/nonce/sig trio prove you hold the signing secret and that the request is fresh.
| Header | Description |
|---|---|
x-zedgi-key | Your API key identifier (the key from step 2) |
x-zedgi-ts | Current epoch time in milliseconds |
x-zedgi-nonce | 32-char random lowercase hex (128-bit, single-use per key) |
x-zedgi-sig | HMAC-SHA256 of {ts}:{nonce}:{hex(sha256(body))} |
Computing the signature â
message = `${ts}:${nonce}:${sha256hex(jsonBody)}`
x-zedgi-sig = HMAC-SHA256(message, signing_secret)The body is the raw JSON string of the RPC payload â not a JSON object, not pretty-printed. Use JSON.stringify(body) to normalize it.
Step-by-step signing â
import { createHash, createHmac, randomBytes } from 'node:crypto';
function signRequest(
body: Record<string, unknown>,
apiKey: string,
signingSecret: string
): Headers {
const ts = Date.now().toString();
const nonce = randomBytes(16).toString('hex'); // 32 hex chars
const bodyJson = JSON.stringify(body);
const bodySha256 = createHash('sha256').update(bodyJson).digest('hex');
const message = `${ts}:${nonce}:${bodySha256}`;
const sig = createHmac('sha256', signingSecret)
.update(message)
.digest('hex');
return new Headers({
'content-type': 'application/json',
'x-zedgi-key': apiKey,
'x-zedgi-ts': ts,
'x-zedgi-nonce': nonce,
'x-zedgi-sig': sig,
});
}import hashlib
import hmac
import json
import secrets
import time
def sign_request(body: dict, api_key: str, signing_secret: str) -> dict[str, str]:
ts = str(int(time.time() * 1000))
nonce = secrets.token_hex(16)
# Compact JSON, same as JSON.stringify
body_json = json.dumps(body, separators=(",", ":"))
body_sha256 = hashlib.sha256(body_json.encode("utf-8")).hexdigest()
message = f"{ts}:{nonce}:{body_sha256}"
sig = hmac.new(
signing_secret.encode("utf-8"),
message.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return {
"content-type": "application/json",
"x-zedgi-key": api_key,
"x-zedgi-ts": ts,
"x-zedgi-nonce": nonce,
"x-zedgi-sig": sig,
}POST /rpc HTTP/1.1
Host: acme.zedgi.app
content-type: application/json
x-zedgi-key: zk_live_1a2b3c4d5e6f7890abcdef1234567890abcdef12
x-zedgi-ts: 1749600123456
x-zedgi-nonce: a1b2c3d4e5f67890abcdef1234567890
x-zedgi-sig: 8f7e6d5c4b3a291807f6e5d4c3b2a190f8e7d6c5b4a39281706f5e4d3c2b1a09
x-zedgi-cred: AQ... (ECIES-encrypted credentials blob â see next section)
{"service":"redis","method":"get","payload":{"args":["mykey"]}}The official clients compute and attach these headers (plus x-zedgi-cred when you pass a credential) automatically â the signing secret is auto-pulled and cached, so you don't supply it. (signingSecret is still accepted if you'd rather manage it yourself; secret is a deprecated alias.)
signingSecret vs publicKey â two keys, two jobs â
These are easy to confuse, so here's the difference:
signingSecret â proves the request is really from you and wasn't tampered with. A shared secret (symmetric, HMAC-SHA256). The client signs each request â HMAC(ts:nonce:sha256(body), signingSecret) â the x-zedgi-sig header. The gateway holds the same secret, recomputes it, and checks they match. That proves the caller holds the secret (authenticity), the body wasn't altered in transit (integrity), and â with the timestamp + single-use nonce â that it isn't a replay. It protects the request itself.
publicKey â encrypts your DB credentials so only ZedGi's gateway can read them. The public half of an X25519 keypair (asymmetric). The client encrypts your credential to this public key (ECIES). The matching private key is held by ZedGi (wrapped with the Master KEK) and is used inside the gateway to decrypt. The gateway then re-encrypts the credential to the proxy node's key for the final hop; the proxy decrypts it transiently to open the TCP connection. So plaintext exists only momentarily inside the gateway and the proxy â never on the network, never at rest. It protects the credential content (confidentiality).
signingSecret | publicKey | |
|---|---|---|
| Type | Symmetric (same secret both sides) | Asymmetric (public encrypts, private decrypts) |
| Job | Authenticate the request; detect tampering/replay | Hide your DB credentials in transit |
| Protects | The request envelope | The credential payload |
| Operation | Signing (HMAC) | Encryption (ECIES) |
| Decrypted/verified by | The gateway (HMAC compare) | The gateway (with your account private key) |
Analogy: the signing secret is a tamper-proof signature on the envelope; the public key is a lockbox only the recipient (the gateway) can open.
5. The credential "link" (zero-knowledge) â
ZedGi never stores your database credentials. Services only record target endpoint metadata: a host and, when you provide one, a port.
Link logic:
- At call time you (or the SDK) supply your real credentials.
- The client encrypts them client-side with your per-account X25519 public key (ECIES).
- If your credential object includes
header, thatheaderobject is excluded from ECIES encryption and sent as signed plaintext metadata for proxy/firewall integrations. - The ciphertext is sent as the
x-zedgi-credheader â this is the "link" that travels with every request. - The gateway re-encrypts the blob (never materializes plaintext) for the specific proxy node.
- Only the proxy transiently decrypts to open the TCP connection to your real Redis/Postgres/MySQL.
The encrypted blob is account-specific and versioned (includes key_version). This enables safe key rotation.
Get your current public key (for encryption) â
Auto-pull (recommended): omit publicKey when creating the client and supplying a credential. The SDK will call:
GET /api/account/keys/current
x-zedgi-key: zk_...and cache the response.
Manual / pinned (faster startup, explicit):
GET /api/account/keys/current
x-zedgi-key: zk_live_...Response:
{
"id": "9f2c...32-hex...",
"key_version": 1,
"public_key": "lRk7...base64url...",
"created_at": 1749600000000
}Copy the public_key into your environment as ZEDGI_PUBKEY (or pass it directly). When pinning manually, also pass accountId (the id above) and keyVersion so the SDK skips the auto-pull entirely.
6. Use the client (with credential linking) â
import { createZedgiClient } from '@zedgi/zedgi-client';
// Full / explicit form â every field annotated. Only `url`, `key`, and `credential`
// are needed in practice; everything else is auto-fetched (see comments).
const zedgi = createZedgiClient({
url: 'https://acme.zedgi.app', // your personal Zedgi endpoint (the gateway) you got at sign-up â NOT your database
key: process.env.ZEDGI_KEY!, // REQUIRED â your API key identifier (zk_...), from dashboard â service â New key. Sent as x-zedgi-key
signingSecret: process.env.ZEDGI_SECRET!, // OPTIONAL â HMAC secret used to sign each request. Auto-pulled + cached when omitted; pin it only to skip the fetch
publicKey: process.env.ZEDGI_PUBKEY!, // OPTIONAL â your account X25519 public key; your credential is encrypted to it (the GATEWAY decrypts). Auto-pulled when omitted
accountId: process.env.ZEDGI_ACCOUNT_ID!, // OPTIONAL â the `id` from /api/account/keys/current. Auto-pulled with publicKey
keyVersion: 1, // OPTIONAL â the `key_version` from the same endpoint. Auto-pulled with publicKey
credential: { // YOUR database's own login (encrypted client-side; Zedgi never sees plaintext)
host: 'my-db.example.com', // OPTIONAL â normally unnecessary: the gateway uses the host you set when registering the service
port: 5432, // OPTIONAL â same as host; defaults to the adapter's standard port
user: 'app', // your DB username
password: process.env.DB_PASSWORD!, // your DB password
database: 'myapp', // the database/schema to use (Postgres/MySQL)
header: { // OPTIONAL â signed-but-NOT-encrypted metadata forwarded to firewalls/proxies
'x-firewall-token': process.env.DB_FIREWALL_TOKEN!,
},
},
cache: true, // OPTIONAL â encrypt the credential once and reuse the ciphertext (default true)
});
// In practice this is all you write â the rest is automatic:
const zedgiAuto = createZedgiClient({
url: 'https://acme.zedgi.app', // your Zedgi endpoint
key: process.env.ZEDGI_KEY!, // your zk_... key
credential: { // your DB secrets (host/port come from the service)
user: 'app',
password: process.env.DB_PASSWORD!,
database: 'myapp',
},
});
// Use exactly like the native driver
const redis = zedgi.redis();
await redis.set('user:42', JSON.stringify({ name: 'Ada' }));
const pg = zedgi.postgres();
const { rows } = await pg.query('SELECT NOW()');From just url + key + credential, the client automatically:
- Pulls + caches your signing secret and account public key (using your
key). - Encrypts
credentialâx-zedgi-cred(the link), then signs every request (x-zedgi-ts+x-zedgi-nonce+x-zedgi-sig).
Minimal (headers managed by you) â
If you only need the RPC facade and will attach headers yourself:
const zedgi = createZedgiClient({
url: 'https://acme.zedgi.app',
key: process.env.ZEDGI_KEY!,
});Raw HTTP (any language / custom signing) â
See the Authentication & Security guide for the exact header computation and blob format.
7. Key rotation â
Rotate your account encryption keypair (the X25519 key your credentials are encrypted to) at any time.
From the dashboard: open Overview and click Rotate key. You'll see the new key version immediately.
From the API:
POST /api/account/keys/rotate
Authorization: Bearer <session>What happens:
- A new active keypair is created; the previous one is marked retired (it keeps working so in-flight credential blobs don't break).
- You receive a notification email.
How the SDK handles it â usually nothing to do:
- A new client instance auto-pulls the active key on first use, so it encrypts to the new key automatically.
- A long-lived client in auto mode (you didn't pin
publicKey) self-heals: if a call ever fails because its cached key is outdated, the SDK drops the cache, re-pulls the current public key, re-encrypts, and retries once. - If you pinned
publicKey/accountId/keyVersionyourself, the auto-recovery is off â fetch the new values fromGET /api/account/keys/currentand update your config.
API keys (zk_* identifier + signing secret) are completely unaffected by rotation â only the credential-encryption key changes.
See the full details in the Authentication & Security guide.
Free tier & pricing â
Every account receives 100 requests per day for free. No credit card required to start.
When you need more, deposit any amount from the Billing page (minimum $5). Usage is billed at $10 per million requests and deducted automatically each night.
See the full Redis, Postgres, and MySQL command references, or learn about custom hooks.
Security model
- API keys use a two-value model: a public identifier (
zk_...) and an HMAC signing secret (shown once). - The "link" (
x-zedgi-cred) carries credentials encrypted client-side with your account X25519 public key (ECIES). ZedGi never stores plaintext credentials. - Requests are protected by timestamp + single-use nonce + HMAC-SHA256 signature.
- The credential blob is re-encrypted at the gateway for the RPC node (double hop). Plaintext exists only transiently inside the proxy when opening the TCP connection to your database.
- Account key rotation is supported with auto public-key discovery in the SDK and a grace period for old blobs.
- All traffic over HTTPS. Optional IP whitelisting per service.