Developers

Quickstart

Provision a tenant, capture consent in the browser, gate your backend. ~10 minutes.

Four steps

Provision, capture, gate, prove

0

Provision (required before anything else works)

The API server validates that every request references a real, active tenant and a §5-approved notice. Skipping this step causes 404 / 422 responses on every subsequent call.

0a — Stand up the server

Self-hosting: clone dpdp-suite and start the stack with Postgres included. If your operator already runs a hosted instance, skip to 0b and use the URL they give you.

# pull and start (Postgres + API in one compose profile)
git clone https://github.com/noidme-git/dpdp-suite
cd dpdp-suite
docker compose --profile pg up -d

# API will be reachable at http://localhost:3000
# (override with DPDP_PORT if you need a different port)

0b — Bootstrap the first operator key

On a fresh database there are no users. Bootstrap creates the root operator account exactly once. Two equivalent methods — use whichever fits your workflow:

# Method A — CLI seed command (interactive; safe to run against a running server)
npm run seed-operator

# Method B — env-var fallback (useful for unattended / container first-boot)
#   Set before the server starts; it creates the operator on startup and is ignored
#   on subsequent boots once the record exists.
DPDP_BOOTSTRAP='{"email":"ops@example.com","password":"…"}' docker compose up -d

Both paths emit an ok_live_… operator key. Store it securely — it is shown once.

0c — Create a tenant and mint API keys in the console

Sign in to the admin console at /console with the operator account, then:

  1. Tenants → New tenant — fill name + domain, save.
  2. Activate the tenant (status must be active before it can accept consent events).
  3. API keys → Mint — the console shows pk_live_… (publishable, browser-safe) and sk_live_… (secret, server-only) once. Copy both now.

See Authentication → for a full breakdown of all five credential types and what each can reach.

0d — Author and approve a §5 notice

Every consent event is linked to an approved notice. Draft yours, then approve it. The notice id returned by /approve is what you put in data-notice.

export API=https://api.noidme.com   # or http://localhost:3000 for self-host
export SK=sk_live_…                 # the secret key you just minted

# 1. Draft the notice
curl -sX POST $API/v1/notices \
  -H "Authorization: Bearer $SK" \
  -H "Content-Type: application/json" \
  -d '{"title":"Marketing consent","purposes":["marketing","analytics"]}' \
  | jq .id
# → "ntc_0abc123…"   ← copy this

# 2. Approve it (status moves to approved; generates the §5 hash)
curl -sX POST $API/v1/notices/ntc_0abc123…/approve \
  -H "Authorization: Bearer $SK"
# → { "id": "ntc_0abc123…", "status": "approved", … }

Only approved notices are accepted by the consent-capture endpoints. Drafts and archived notices return 422.

1

Add the consent banner (browser)

Drop the embed with your publishable key and the notice id from step 0d. It blocks non-essential scripts until consent and records the choice against the approved notice.

<script src="https://cdn.noidme.com/dpdp-embed.js"
  data-api="https://api.noidme.com"
  data-key="pk_live_…your publishable key…"
  data-notice="ntc_…your approved notice id…"
  defer></script>
Identity-stitching: the embed also accepts a data-principal attribute (or the noidme.init({ principalId }) JS call after the script loads) so you can tie a consent record to your own stable, pseudonymous identifier — for example a UUID you store in a first-party cookie. Use the same principalId in your backend gate (step 2). The API rejects PII-shaped values (email address, phone number, national ID) with 422 PII_PRINCIPAL — use an opaque handle instead.
Want consent to drive network enforcement too? A turnkey consent-sdk adapter lets captured consent decisions flow directly into noidme.js enforcement — so the same banner that records the §6(10) receipt also unblocks (or keeps blocked) outgoing requests in the browser. This is Layer-1 honest-SDK enforcement: reliable against eager third-party tags, not against deliberately hostile in-page scripts. See noidme.js →
2

Check consent before processing (server)

The hard gate. Allow/deny plus the §6(10) tamper-evident proof. Use the same principalId the embed recorded under.

curl -sX POST $API/v1/consent/can-process \
  -H "Authorization: Bearer $SK" \
  -H "Content-Type: application/json" \
  -d '{
    "principalId": "usr_7f3a…",           # same opaque id the embed used
    "noticeId":    "ntc_0abc123…",         # the approved notice from step 0d
    "purpose":     "marketing"
  }'
# → { "allowed": true, "reason": "granted",
#     "proof": { "seq": 42, "hash": "9f3c…", "noticeId": "ntc_0abc123…" } }

Or with the Node SDK:

import { createClient } from "@noidme/dpdp-server-sdk";

const dp = createClient({
  baseUrl:  process.env.API,   // https://api.noidme.com
  apiKey:   process.env.SK,    // sk_live_…
});

const { allowed } = await dp.consent.canProcess({
  principalId: "usr_7f3a…",          // same opaque id as the embed
  noticeId:    "ntc_0abc123…",       // approved notice id
  purpose:     "marketing",
});

if (allowed) {
  // …safe to process
}
3

Prove it later (audit)

Pull a regulator-ready evidence pack for a principal, or verify the entire consent chain integrity:

# Evidence pack for a single principal (returns signed PDF + JSON trail)
curl -sX POST $API/v1/admin/evidence-pack \
  -H "Authorization: Bearer $SK" \
  -d '{"principalId":"usr_7f3a…"}'

# Full ledger integrity check (tamper-evident hash chain)
curl -sX POST $API/v1/admin/verify-chain \
  -H "Authorization: Bearer $SK"

Next steps

Wire your real keys, explore every endpoint, handle principal rights, or browse an SDK.