FR EN

Gate a feature in 5 minutes

The goal: before your code uses a piece of personal data for a purpose, it asks Agreely whether the person licensed that use, and respects the answer. The whole integration is one call.

1. Install and authenticate

npm install @agreely/sdk      # or: composer require agreely/sdk
export AGREELY_API_KEY=agr_live_xxx

A check-scoped key is all you need to gate features.

2. Find the cell

A consent cell is a category of data crossed with a purpose: Phone number x Billing. Use the exact human labels your catalog declares; the server normalizes them (trim, collapse whitespace, lowercase, ignore accents), so casing, spacing and accents do not matter. The French label is accepted, and the English label is when the company declared one for the cell; an undeclared label denies, and so does an ambiguous one, which after normalization names more than one cell (two labels that differ only by their accents, for instance). Never normalize them yourself.

agreely catalog --json    # discover your declared (category, purpose) cells

3. Wrap the data use

import { Agreely } from "@agreely/sdk";

const agreely = new Agreely({ apiKey: process.env.AGREELY_API_KEY! });

async function chargeCard(customerId: string) {
  if (!(await agreely.check(customerId, "Phone number", "Billing"))) {
    return; // denied: do not use the phone number for billing
  }
  // allowed: proceed
}

check() returns a boolean and allow is the only true. A revoked, expired, or erased cell is false, and so is a consent-based cell that was never granted. A cell declared on a basis other than consent answers allow with the status necessity.

4. Handle the reasons when it matters

For UX or logging, checkDetailed() tells you why:

const d = await agreely.checkDetailed(customerId, "Phone number", "Billing");
switch (d.status) {
  case "active":     /* allow */ break;
  case "necessity":  /* allow on the declared non-consent basis (d.basis) */ break;
  case "none":       /* never granted: ask for consent */ break;
  case "revoked":    /* withdrawn: stop, never re-ask automatically */ break;
  case "expired":    /* lapsed: re-ask */ break;
  case "relationship_ended":         /* P-39.1 s. 12 / A-2.1 s. 65.1, first para.: use ends with the purposes; do not re-ask automatically */ break;
  case "requires_depersonalization": /* depersonalized-only ground: never answer it by asking consent */ break;
  case "basis_not_in_regime":        /* the catalog cell needs a re-declaration */ break;
  default:                           /* an unknown status: treat it as a deny */ break;
}

Always branch on d.decision for the decision itself; the status is the why. An answer backed by a consent also carries d.tier (full, manual or verbal) and d.validUntil, the consent's end: your code decides which tier a use accepts, and validUntil is never a cache lifetime. The full vocabulary is in the API reference.

A deny is a normal 200. checkDetailed returns it; it only throws on a refused key or one without the needed scope (401, 403), a suspended subscription (402), a validation error, a rate limit, or an outage.

5. Know the failure mode

The check is fail-closed. If Agreely is unreachable, check() returns false (deny) and checkDetailed() throws AgreelyUnavailableError. That is the safe default: an outage never silently grants access. The same goes when the company's billing is suspended (a 402): check() returns false and checkDetailed() throws AgreelyBillingInactiveError. If a specific, low-risk category genuinely needs to keep working through an outage, see handling outages for the deliberate, audited fail-open exception.

Next