Skip to content

API reference · v0.1 draft

Design-partner draft - endpoints may change before v1

The consent ledger API

Consentry records every consent decision in an append-only ledger, answers "is this use of personal data permitted?" at request time, and produces a regulator-grade evidence trail on demand. Decide once. Prove anywhere.

Legal defines the purposes. Engineering starts with three calls: record, check, evidence. The ledger is the audit trail.

The whole integration
# 1 - record the decision
curl -X POST https://api.consentry.io/v1/decisions \
  -H "Authorization: Bearer $CONSENTRY_KEY" \
  -d '{ "subject": "usr_8492", "purpose": "marketing", "status": "granted" }'

# 2 - check before you use the data
curl -X POST https://api.consentry.io/v1/checks \
  -d '{ "subject": "usr_8492", "purpose": "marketing" }'
#    → { "decision": "allow", "evidence": "evd_01J8ZK…" }

# 3 - prove it, whenever anyone asks
curl https://api.consentry.io/v1/evidence?subject=usr_8492

Quickstart

From zero to a provable consent record in under ten minutes.

  1. Get an API key

    Design partners receive keys directly. Write to support@consentry.in. Keys are scoped per environment: sk_test_ keys never write to your production ledger.

  2. Install the SDK

    Terminal
    npm install @consentry/node   # or: pip install consentry
  3. Record a decision

    Call this wherever consent is captured today: your signup flow, cookie banner, preference center, or sales-signed DPA. The source doesn't matter; the ledger unifies them.

    record.ts
    import Consentry from "@consentry/node";
    const consentry = new Consentry(process.env.CONSENTRY_KEY);
    
    await consentry.decisions.record({
      subject: "usr_8492",          // your user ID - we never need PII
      purpose: "marketing",
      status:  "granted",
      basis:   "consent",           // consent basis under your notice
      source:  "signup_form_v3",
    });
  4. Check, then prove

    Gate any data use with check(). Every call is itself logged, so your evidence trail shows not just what was consented, but that it was enforced.

    enforce.ts
    const { decision } = await consentry.check({
      subject: "usr_8492",
      purpose: "marketing",
    });
    
    if (decision === "allow") sendCampaignEmail(user);

    When an auditor, regulator, or enterprise customer asks for proof, one call returns the complete, tamper-evident history:

Core concepts

Four objects. Everything in the API is one of these.

Subject

The person the data is about, identified by your ID. Consentry stores no names, emails, or PII. The ledger references identifiers; it never contains PII.

Purpose

A named use of personal data: marketing, analytics, processor_sharing, ai_training. Defined once by legal, referenced everywhere by code.

Decision

An immutable event: subject × purpose × status × consent basis × source × timestamp. Decisions are never edited. A change is a new decision, and the history is the point.

Evidence

A signed, hash-chained export of every decision and enforcement check for a subject, purpose, or date range. The artifact you hand to an auditor.

Why append-only matters. A consent database that can be edited proves nothing. The Consentry ledger is hash-chained: each record commits to the one before it, so any change after the fact is detectable by a third party, without trusting us, or you.

Identity & binding

The ledger references subjects by your opaque IDs. Consentry never stores PII. That raises a fair question: what stops the mapping between usr_8492 and a real person from changing after the fact?

Identity commitments.

When recording a decision, optionally include a commitment: an HMAC-SHA256 of a stable identifier (typically the email address), keyed with a secret only you hold. The commitment is fixed into the hash chain at write time. Consentry still sees no PII, but at audit time, revealing the identifier and key lets anyone recompute the hash and verify the binding existed when the decision was recorded. Remapping an ID later changes nothing that matters: the commitment already sealed who the decision was about.

Computing a commitment
import { createHmac } from "crypto";

const commitment = createHmac("sha256", process.env.BINDING_KEY)
  .update("priya@example.com")      // stays on your side, always
  .digest("hex");

Identity events are ledger entries.

Real systems remap IDs for legitimate reasons: account merges, migrations, re-registrations. Record a merge with subject.merged so the identity lineage sits in the same tamper-evident history as the decisions. When you alias or migrate IDs, record those lineage changes as ledger entries too. Nothing about a subject (decisions or identity) changes without leaving a record.

Scope of the guarantee. Consentry is tamper-evident, not tamper-proof. If records are falsified at the moment of entry, no vendor can detect that, including us. What the ledger guarantees is narrower and provable: once recorded, nothing can be altered, backdated, or remapped without detection.

Authentication

All requests use Bearer authentication over HTTPS. Test and live environments are fully separated.

Authorization header
Authorization: Bearer sk_live_c0ns3ntry…
API key environments
Key prefixEnvironmentWrites to ledger
sk_test_SandboxSandbox ledger only, purged nightly
sk_live_ProductionAppend-only production ledger

Scoped keys. Because the ledger is append-only, a leaked write key can't erase history, but it could pollute it. Keys are therefore scoped by capability (decisions:write, checks:run, evidence:read) and, optionally, by purpose. Issue the narrowest key each system needs, rotate on schedule, and if a key is compromised, revoke it and file a dispute against any records it wrote. The poisoning and the cleanup both stay on the record. See POST /v1/disputes.

Endpoints

POST/v1/decisions

Record a consent decision. Returns the immutable decision record with its ledger position.

FieldTypeDescription
subjectrequiredstringYour identifier for the person. Opaque to Consentry.
purposerequiredstringA purpose defined in your policy. Unknown purposes are rejected.
statusrequiredenumgranted · revoked · pending
basisoptionalstringGround for the decision under your DPDP notice. Defaults to consent.
sourceoptionalstringWhere the decision was captured: a form ID, banner version, or document reference. Strongly recommended: it's what auditors ask about first.
expires_atoptionaltimestampAutomatic transition to expired; emits a webhook.
commitmentoptionalstringHMAC-SHA256 identity commitment, sealed into the hash chain at write time. See Identity & binding.
captured_atoptionaltimestampWhen consent was actually captured, for batch imports and offline flows. Client-asserted: always labelled as such in evidence, and shown alongside the sealed recorded_at. A large or recurring gap between the two is surfaced as an audit flag, not hidden.
Response · 201
{
  "id": "dec_01J8ZKQ4T…",
  "subject": "usr_8492",
  "purpose": "marketing",
  "status": "granted",
  "basis": "consent",
  "ledger_index": 1482231,
  "recorded_at": "2026-07-19T14:02:11Z",   // sealed by the chain
  "receipt": {                              // keep this - it's your independent proof
    "chain_hash": "9f2c41ab…",
    "signature": "MEUCIQDx…"
  }
}
GET/v1/decisions/:id

Retrieve a single decision by ID, including its position in the hash chain and the record that superseded it, if any.

Response · 200
{
  "id": "dec_01J8ZKQ4T…",
  "subject": "usr_8492",
  "purpose": "marketing",
  "status": "granted",
  "basis": "consent",
  "ledger_index": 1482231,
  "recorded_at": "2026-07-19T14:02:11Z",
  "superseded_by": null
}
POST/v1/checks

Ask "is this use permitted right now?" before touching the data. Median latency target <30ms; every check is itself written to the ledger, turning enforcement into evidence.

FieldTypeDescription
subjectrequiredstringThe person whose data would be used.
purposerequiredstringThe intended use.
contextoptionalobjectFree-form metadata (system, campaign, model run) recorded with the check.
Response · 200
{
  "decision": "allow",        // or "deny" · "review"
  "reason": "granted:consent",
  "valid_for": 86400,          // seconds - allow answers expire
  "evidence": "evd_01J8ZK…"    // this check's own audit reference
}

Allow answers expire. Every allow carries a TTL and the SDK refuses to serve it from cache beyond it. A cached "yes" from before a revocation is the oldest hole in consent systems, and it's closed by design. After a revocation webhook is acknowledged, any use past the TTL is provably against a recorded deny.

Failure mode is your choice, per purpose. If the check endpoint is unreachable, the SDK follows an explicit setting: fail_closed (block the use, right for sensitive purposes) or fail_cached (serve the last synced policy within its TTL, right for high-volume, low-risk purposes). There is no silent fail_open. Checks answered from cache are backfilled to the ledger when connectivity returns, flagged as cached. Outages leave a record too.

GET/v1/evidence

Primary proof endpoint. Generate a signed, human-readable evidence bundle (every decision and every enforcement check, hash-verified), filtered by subject, purpose, or date range. Formats: json, pdf, csv.

Query paramDescription
subjectoptionalLimit to one person: the DSAR and complaint case.
purposeoptionalLimit to one use: "show me everyone in the marketing pool, with proof."
from / tooptionalAudit window.
formatoptionalpdf produces the regulator-facing report with verification instructions on page one.

What a bundle claims, exactly. Every export states its scope on page one: a complete, tamper-evident record of what was submitted to the ledger, with client-asserted fields labelled and coverage metrics attached. It never claims to describe events outside the ledger. That precision is what lets an auditor rely on it. See Trust & verification.

POST/v1/disputes

Contest a record without editing it. A dispute is an appended annotation linking to one or more decisions, for compromised keys, integration bugs, or data entered in error. Evidence bundles show disputed records with their dispute attached, so the mistake and the correction are both on the record. This is the only correction mechanism; there is deliberately no delete.

FieldTypeDescription
decision_idsrequiredarrayThe records being contested.
reasonrequiredenumcompromised_key · integration_error · data_entry_error · subject_contest
noteoptionalstringHuman-readable context, included in evidence exports.

Trust & verification

A proof layer has to answer one more question: why should anyone trust the proof layer? The design assumes they shouldn't have to.

External anchoring

Every 24 hours, the current chain head is published to an independent, public timestamping service (RFC 3161) and a public transparency log. Rewriting any past record would require forging both Consentry's chain and the external anchors, which are outside our control. Verification instructions ship in every evidence bundle, so an auditor can confirm integrity without contacting us at all.

Write receipts

Every successful write returns a signed receipt containing the record's chain position and hash. Keep these, even just logged, and you hold independent proof of what the ledger contained at write time. If Consentry ever presented a different history, your receipts would expose it. We designed the system so that our own dishonesty would be detectable by our customers.

Coverage metrics

Tamper-evidence proves nothing was changed; it can't prove everything was recorded. So instead of pretending otherwise, coverage is measured and shown: decisions vs. checks per purpose, gated vs. ungated egress points reported by the SDK, and revocation-to-acknowledgment latency. Gaps appear on your dashboard and, at your option, in evidence bundles. A record with visible, measured boundaries is worth more to an auditor than one with invisible ones.

Erasure without erasing history

Right-to-erasure requests meet an immutable ledger through crypto-shredding. Identity commitments are derived from a per-subject key; on an erasure request, that key is destroyed and its destruction is itself recorded as a ledger event. The commitments become permanently unverifiable (cryptographically severed from any person) while the chain's mathematical integrity, and your proof that consent was handled correctly, both survive. Review this mechanism with your counsel; the design intent is that the ledger holds no personal data at all once the key is gone.

The honest boundary. Consentry makes falsification after the fact detectable, and makes gaps in coverage visible. No system, ours or anyone's, can detect records falsified at the moment of entry. We say exactly what we prove, which is why what we prove holds up.

Webhooks

Push consent changes to the systems that must obey them (CRM suppression lists, CDP audiences, ad platform exclusions) instead of polling.

Webhook events
EventFires when
decision.grantedA purpose transitions to granted for a subject.
decision.revokedConsent is withdrawn. Your handler's job: stop the use, fast. The ledger timestamps both the revocation and your acknowledgment.
decision.expiredAn expires_at deadline passes.
subject.mergedTwo subject IDs were linked as the same person. Identity lineage recorded in the ledger. See Identity & binding.
evidence.exportedAn evidence bundle was generated, so exports are themselves auditable.

Delivery is verified. Every webhook is signed, and your endpoint's acknowledgment is recorded in the ledger. When a regulator asks "when did the ad platform stop receiving this user's data?", the answer is a timestamp, not an estimate.

Errors

Errors are precise about what happened and how to fix it. Consent infrastructure that fails vaguely is a liability.

API error codes
CodeMeaning
400 unknown_purposeThe purpose isn't defined in your policy. Add it in the policy file, or check for a typo. Purposes are case-sensitive.
401 invalid_keyKey is missing, malformed, or for the wrong environment.
409 supersededYou referenced a decision that a newer decision has replaced. Fetch current state via /subjects/:id/consent.
422 immutable_recordDecisions cannot be edited or deleted. Record a new decision instead. That's the design, not a limitation.

Decide once. Prove anywhere.

The consent decision-and-proof layer.

Request early access for your platform team, or write with design-partner questions.

Support · support@consentry.in