dabadataDocs
Concepts / Call dabadata from your backend

Concepts

Call dabadata from your backend

Your own server code - a Vercel function, a Cloudflare Worker, a Lambda, a box of your own - talks to dabadata over plain HTTPS. There are three situations, and each has its own credential: acting for a signed-in user, acting as your backend, and reacting to a change. Pick the one that fits and you never need the secret key in a request path.

Your code ...ExampleSendsRules
acts for a signed-in userAn AI summary of my noteThe user's token (forwarded) and the publishable keyApply, as that user
acts as the backendA Stripe webhook marks an order paid; a nightly importA restricted keyApply, as the key's role
reacts to a changeMail a receipt when an order is paidNothing: dabadata calls you (webhooks)-

The secret key bypasses every rule and manages the project. Keep it for deploys, migrations and minting user tokens after your own sign-in (below), not for day-to-day backend work.

The examples use fetch and nothing else, so they run anywhere. The JavaScript client works on a server too: pass the credential as token (it is kept in memory where there is no localStorage); subscribe is the one thing a key cannot do (keys never open a live socket).

Acting for a signed-in user

The browser already holds the user's token. Send it along with the request to your function, and forward it to dabadata with the publishable key: every read and write runs under that user's rules, and the trace shows the user, not your server.

A Next.js route on Vercel that summarises one of the user's notes:

ts
// app/api/summary/route.ts
const DABADATA = "https://dabadata.com";
const KEY = process.env.DABADATA_PUBLISHABLE_KEY!; // pk_live_...

export async function POST(req: Request) {
  const auth = req.headers.get("authorization"); // "Bearer <the user's token>"
  if (!auth) return new Response("sign in first", { status: 401 });
  const { noteId } = await req.json();

  // Read as the user: their read rule decides (a 404 or 403 is theirs, not yours).
  const res = await fetch(`${DABADATA}/v1/db/notes/${encodeURIComponent(noteId)}`, {
    headers: { "X-Api-Key": KEY, Authorization: auth },
  });
  if (!res.ok) return new Response(await res.text(), { status: res.status });
  const { row } = await res.json();

  const summary = await summarize(row.body); // your model call

  // Write back as the user, under their update rule.
  await fetch(`${DABADATA}/v1/mutate`, {
    method: "POST",
    headers: { "X-Api-Key": KEY, Authorization: auth, "Content-Type": "application/json" },
    body: JSON.stringify({ table: "notes", op: "update", id: noteId, values: { summary } }),
  });
  return Response.json({ summary });
}

To know who the user is - for a hosted sign-in (end-user accounts) - ask dabadata; tokens are opaque, so there is nothing to check locally:

ts
const me = await fetch("https://dabadata.com/v1/auth/users/me", {
  headers: { "X-Api-Key": KEY, Authorization: auth },
});
if (me.status === 401) return new Response("signed out", { status: 401 });
const { user } = await me.json(); // { id, email, name, role, ... }

With a provider's JWT (Clerk, Auth0, Firebase, your own) verify it with the provider's library as you do today, and forward it the same way: dabadata checks it against your issuers.

Acting as the backend

Some work has no user behind it: a payment provider's webhook, a cron job, an import. Make a restricted key for it, with a role that may do just that job, and the cleanest way to grant it is an action:

ts
roles: ["admin", "member", "payments"],
actions: {
  mark_paid: {
    allow: { role: "payments" },
    params: { order: { type: "ref", table: "orders" }, paymentId: { type: "text" } },
    steps: [{ op: "update", table: "orders", id: "$params.order", values: { status: "paid", payment_id: "$params.paymentId" } }],
  },
},
bash
# Once, with the secret key (or on the console: Keys, New restricted key):
curl -X POST https://dabadata.com/v1/project/keys/restricted \
  -H "Authorization: Bearer $DABADATA_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"stripe-webhook","role":"payments"}'
# -> { "key": "rk_live_...", ... }  store it as DABADATA_PAYMENTS_KEY

The Stripe webhook on Vercel then calls the action. If the key leaks, it can mark orders paid and nothing else, and you revoke it alone:

ts
// app/api/stripe/route.ts
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const body = await req.text();
  const event = stripe.webhooks.constructEvent(body, req.headers.get("stripe-signature")!, process.env.STRIPE_WEBHOOK_SECRET!);
  if (event.type === "checkout.session.completed") {
    const s = event.data.object;
    const res = await fetch("https://dabadata.com/v1/actions/mark_paid", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.DABADATA_PAYMENTS_KEY}`, "Content-Type": "application/json" },
      body: JSON.stringify({ params: { order: s.client_reference_id, paymentId: s.payment_intent } }),
    });
    if (!res.ok) return new Response(await res.text(), { status: 500 }); // Stripe retries
  }
  return new Response(null, { status: 204 });
}

A nightly job as a Cloudflare Worker with a Cron Trigger, using its own key (role importer):

ts
export default {
  async scheduled(_event: ScheduledEvent, env: { DABADATA_IMPORT_KEY: string }) {
    const rates = await (await fetch("https://api.example.com/rates")).json();
    const res = await fetch("https://dabadata.com/v1/transact", {
      method: "POST",
      headers: { Authorization: `Bearer ${env.DABADATA_IMPORT_KEY}`, "Content-Type": "application/json" },
      body: JSON.stringify({
        ops: rates.map((r: { code: string; rate: number }) => ({
          table: "rates", op: "update", id: r.code, values: { rate: r.rate },
        })),
      }),
    });
    if (!res.ok) throw new Error(`dabadata ${res.status}: ${await res.text()}`);
  },
};

Every call shows up in the trace as the user key:stripe-webhook with role payments, so "who changed this order?" has an answer. A refusal comes back with the rule's explanation, as for anyone else.

Reacting to a change

A serverless function cannot keep a live socket open, and keys cannot open one. Declare a webhook instead: dabadata calls your endpoint after the write commits, signs the request, retries it, and logs every attempt.

ts
webhooks: {
  orderPaid: {
    on: { table: "orders", ops: ["update"], where: "status = 'paid'", changed: ["status"] },
    url: "https://shop.example.com/api/order-paid",
  },
},
ts
// app/api/order-paid/route.ts
import { Webhook } from "standardwebhooks";
const wh = new Webhook(process.env.DABADATA_WEBHOOK_SECRET!); // the hook's whsec_... secret

export async function POST(req: Request) {
  const body = await req.text();
  let event: any;
  try {
    event = wh.verify(body, Object.fromEntries(req.headers));
  } catch {
    return new Response("bad signature", { status: 400 });
  }
  if (await alreadyHandled(req.headers.get("webhook-id")!)) return new Response(null, { status: 204 }); // retries reuse the id
  await mailReceipt(event.data.row);
  return new Response(null, { status: 204 });
}

When the handler has to write back - "receipt sent" - give it a restricted key too, as in the previous section.

Signing users in with your own backend

If your backend signs people in its own way (not dabadata's hosted sign-in, not a provider JWT), it mints a dabadata user token for each signed-in user with the secret key, and hands the pair to the browser. This is the one routine job that needs the secret key, and it never leaves your server:

ts
export async function dabadataTokenFor(user: { id: string; name: string }) {
  const res = await fetch("https://dabadata.com/v1/users/token", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.DABADATA_SECRET_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ id: user.id, name: user.name, role: "member" }),
  });
  if (!res.ok) throw new Error(`dabadata: ${res.status} ${await res.text()}`);
  return res.json(); // { token, expiresAt, refreshToken, refreshExpiresAt }
}

See Identity for refresh tokens and revoking. The console's Keys page has these snippets filled in with your project's values (Snippets, Node backend).

Which credential where

CredentialWhere it livesWhat it can do
Publishable key pk_Browsers, apps, and next to a forwarded user tokenNames the environment; anonymous on its own
User tokenThe signed-in user's browser; forwarded by your functionsWhat the user's rules allow
Restricted key rk_One backend job's environment variablesWhat its role's rules and actions allow; no sockets, no project management
Webhook signing secret whsec_The endpoint that receives a hookNothing on dabadata: it only proves a request came from it
Secret key sk_Your deploy pipeline and the token-minting routeEverything, bypassing rules
dabadata documentation, written against API contract v1.