Skip to content

Get started with ZedGi ​

Connect any TCP database to the edge in minutes. No open ports. No VPNs. Just HTTPS.

Trusted by teams at Vercel, Linear, and 140+ others

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:

ValueHeaderPurpose
keyx-zedgi-keyPublic 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.

bash
# 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."
}
â„šī¸Ž
Keys are encrypted at rest with AES-256-GCM. They are only decrypted inside the Cloudflare Worker runtime.

3. Install the client ​

Terminal
bash
npm install @zedgi/zedgi-client

4. 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.

HeaderDescription
x-zedgi-keyYour API key identifier (the key from step 2)
x-zedgi-tsCurrent epoch time in milliseconds
x-zedgi-nonce32-char random lowercase hex (128-bit, single-use per key)
x-zedgi-sigHMAC-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 ​

ts
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,
  });
}
python
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,
    }
http
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 when you provide secret (and the credential material).

ZedGi never stores your database credentials. Services only record host + 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).
  • The ciphertext is sent as the x-zedgi-cred header — 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):

http
GET /api/account/keys/current
x-zedgi-key: zk_live_...

Response:

json
{
  "key_version": 1,
  "public_key": "lRk7...base64url...",
  "created_at": 1749600000000
}

Copy the public_key into your environment as ZEDGI_PUBKEY (or pass it directly).

6. Use the client (with credential linking) ​

TypeScript
ts
import { createZedgiClient } from '@zedgi/zedgi-client';

// Option A — explicit (pin the public key you copied from the dashboard)
const zedgi = createZedgiClient({
  url: 'https://acme.zedgi.app',
  key: process.env.ZEDGI_KEY!,           // the public identifier (zk_...)
  secret: process.env.ZEDGI_SECRET!,     // the signing secret (used locally only)
  publicKey: process.env.ZEDGI_PUBKEY!,  // account X25519 public key
  credential: {
    host: 'my-db.example.com',
    port: 5432,
    user: 'app',
    password: process.env.DB_PASSWORD!,
    database: 'myapp',
  },
  cache: true,   // encrypt once, reuse ciphertext (default)
});

// Option B — auto public key pull (SDK fetches on first use)
const zedgiAuto = await createZedgiClient({
  url: 'https://acme.zedgi.app',
  key: process.env.ZEDGI_KEY!,
  secret: process.env.ZEDGI_SECRET!,
  credential: { /* your creds */ },
});

// 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()');

The client automatically:

  • Encrypts credential → x-zedgi-cred (the link)
  • Computes x-zedgi-ts + x-zedgi-nonce + x-zedgi-sig using your secret

Minimal (headers managed by you) ​

If you only need the RPC facade and will attach headers yourself:

ts
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 at any time (dashboard or POST /api/account/keys/rotate).

What happens:

  • A new active keypair is created. Old one is marked retired.
  • You receive an email with a link to the dashboard.
  • All future x-zedgi-cred blobs must use the new public key.

Developer action:

  1. Fetch the new public key (auto-pull in the SDK, or GET /api/account/keys/current, or copy from dashboard).
  2. Re-create your client instance (or re-encrypt) with the new publicKey + your credential.
  3. Deploy. Old blobs using retired key versions will be rejected after the grace window.

API keys (zk_* identifier + signing secret) are completely unaffected. You only update the credential encryption material.

The SDK will detect an outdated key (412 response) in auto mode and refetch + retry with the fresh public key where possible.

See the full details in the Authentication & Security guide.

7. Key rotation ​

You can rotate your account X25519 keypair at any time from the dashboard or via the API:

http
POST /api/account/keys/rotate
Authorization: Bearer <session>

This retires the current active keypair and generates a new one. You must then:

  1. Fetch the new public key via GET /api/account/keys/current
  2. Re-encrypt your database credentials with the new public key
  3. Update your client configuration
âš ī¸Ž
API keys are not affected by key rotation — API keys are independent of your account ECIES keypair. You only need to update the x-zedgi-cred value after rotation.

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.