MisarMisar Docs
MisarMailMisar.BlogMisarReachMisarPostMisar.DevMisarSEOMisar PlatformMisar SSO

Consent Records

POST /io/consent — server-side consent ledger for GDPR Art. 7(1) proof of consent across all Misar products.

Every cookie-banner interaction across the Misar suite is recorded server-side as an immutable consent event. This is the audit trail that proves when a visitor granted, updated, or withdrew consent, for which categories, and under which policy version (GDPR Art. 7(1) burden of proof).

POST https://api.misar.io/io/consent
GET  https://api.misar.io/io/consent

The shared @misar/ui cookie banner posts here automatically — most products need no integration work. Use the API directly (or the @misar/consent SDK) for custom consent surfaces.

Record a consent event

POST https://api.misar.io/io/consent

Public — no authentication required. Anonymous visitors must be able to record consent. If an SSO bearer token is sent (Authorization: Bearer <token>), the event is additionally linked to that user id.

The server derives two fields itself — they cannot be supplied by the client:

  • ip_hash — SHA-256 of the client IP with a server-side salt. The raw IP is never stored.
  • user_agent — captured from the request.

Body parameters

productstringrequired

Which Misar product the banner belongs to. One of io, mail, blog, dev, reach, post, tools, id.

domainstringrequired

Hostname the consent was given on, e.g. www.misar.io or misarmail.com.

actionstringrequired

granted (first opt-in), updated (preferences changed), or withdrawn (all optional categories revoked).

categoriesobjectrequired

Category → boolean map of the visitor's choices, e.g. { "necessary": true, "analytics": true, "marketing": false, "functional": true }. 1–16 keys; keys must be simple identifiers.

policy_versionstringrequired

Version of the cookie/privacy policy the visitor consented to, e.g. 2026-07-05.

anonymous_idstring

Banner-generated identifier (a localStorage UUID) that correlates a signed-out visitor's grant with a later withdrawal. Max 64 chars, [a-z0-9-].

Request

curl -X POST "https://api.misar.io/io/consent" \
  -H "content-type: application/json" \
  -d '{
    "product": "mail",
    "domain": "misarmail.com",
    "action": "granted",
    "categories": { "necessary": true, "analytics": true, "marketing": false, "functional": true },
    "policy_version": "2026-07-05",
    "anonymous_id": "3f9c2a1e-8b4d-4c6a-9e2f-1a7b5d3c8e0f"
  }'

Response

Returns 201 Created:

{
  "id": "5e0b2a4c-7d1f-4b9a-8c3e-2f6a9d1b4e7c",
  "created_at": "2026-07-05T12:34:56.789Z"
}
idstring

Id of the stored consent event.

created_atstring

Server timestamp of the event (ISO 8601).

Get consent history

GET https://api.misar.io/io/consent

SSO bearer only — returns the token owner's own consent history, newest first, cursor-paginated in the same shape as /io/wallet/transactions.

Query parameters

limitnumberquery

Items per page. Default 20, max 100.

cursorstringquery

Pagination cursor. Pass the nextCursor from the previous response to fetch the next page.

Request

curl "https://api.misar.io/io/consent?limit=20" \
  -H "Authorization: Bearer $SSO_TOKEN"

Response

{
  "items": [
    {
      "id": "5e0b2a4c-7d1f-4b9a-8c3e-2f6a9d1b4e7c",
      "created_at": "2026-07-05T12:34:56.789Z",
      "product": "mail",
      "domain": "misarmail.com",
      "action": "granted",
      "categories": { "necessary": true, "analytics": true, "marketing": false, "functional": true },
      "policy_version": "2026-07-05"
    }
  ],
  "nextCursor": null
}
itemsobject[]

Consent events, newest first. ip_hash and user_agent are never returned.

nextCursorstring | null

Cursor for the next page, or null when there are no more pages.

Rate limit

Writes are limited to 30 requests per IP per 60 s. Exceeding the limit returns 429 with a Retry-After header.

SDK

import { createConsentClient } from "@misar/consent";

const consent = createConsentClient(); // defaults to https://api.misar.io/io/consent

// Record (public; pass bearerToken to link a signed-in user)
await consent.recordConsent({
  product: "mail",
  domain: "misarmail.com",
  action: "granted",
  categories: { necessary: true, analytics: true, marketing: false },
  policyVersion: "2026-07-05",
  anonymousId: anonId,
});

// Own history (SSO bearer required)
const { items, nextCursor } = await consent.getConsentHistory(token, { limit: 20 });

Notes

  • The ledger is append-only: withdrawing consent adds a withdrawn event, it never deletes the original grant.
  • Events survive account deletion de-identified — user_id is set to null, the event itself is retained as evidence.
  • Errors from the banner never block the visitor: the shared banner posts fire-and-forget.