Skip to content
ConsentOS

Developer integration

Ask before you use data.

Integrating ConsentOS takes four calls: announce your service, request permission, enforce the grant before doing the work, and verify receipts when you need proof. The @consentos/sdk package wraps them with no dependencies.

  1. 1. Create a client — on your server

    Your API key identifies your service and never leaves your backend. In this build Pixly is seeded as a verified service; a full deployment would issue keys from a developer console.

    consent.ts
    import { ConsentOS } from "@consentos/sdk"
    
    export const consent = new ConsentOS({
      serviceId: "pixly",
      apiUrl: "https://consentos.vercel.app",
      apiKey: process.env.CONSENTOS_API_KEY, // server-side only
    })
  2. 2. Request permission

    Say exactly what you want: the data, the purpose, how long you keep it, whether it leaves your company. The user's rules decide deterministically. You get a decision, a machine-readable reason and — for ALLOW and DENY — a signed receipt. An ALLOW receipt is your grant.

    enable-recommendations.ts
    const result = await consent.request({
      userId,
      dataType: "uploaded_images",
      purpose: "personalization",
      retentionDays: 30,
      thirdPartySharing: false,
    })
    
    switch (result.decision) {
      case "ALLOW":
        await saveGrant(userId, "personalization", result.receiptId)
        break
      case "DENY":
        showBlocked(result.reason) // e.g. RETENTION_EXCEEDS_LIMIT
        break
      case "REQUIRE_USER":
        // the user's rules want to be asked; they answer in ConsentOS
        const answer = await consent.waitForDecision(result.requestId)
        break
    }
    POST /api/v1/consent/evaluate → 200
    {
      "requestId": "0f6c1d0e-…",
      "decision": "DENY",
      "reasonCode": "PURPOSE_DENIED",
      "reason": "Foundation-model training is blocked by the user's privacy policy.",
      "details": { "rule": "policy.foundationModelTraining" },
      "evaluatedAt": "2026-09-26T14:32:08.114Z",
      "policyVersion": 3,
      "receiptId": "a1d4…",
      "receiptUrl": "https://consentos.vercel.app/receipts/a1d4…",
      "engineVersion": "1.0.0"
    }
  3. 3. Enforce before you do the work

    This is the part that makes consent real. Your protected endpoint asks ConsentOS whether the grant exists, belongs to this user and service, covers this purpose and data, is not revoked, and still verifies. If not, refuse. Do not trust a decision your own frontend reports.

    app/api/train-model/route.ts
    export async function POST(req: Request) {
      const { grantId } = await req.json()
      const check = await consent.checkGrant({
        userId,
        purpose: "foundation_model_training",
        dataType: "uploaded_images",
        receiptId: grantId,
      })
    
      if (!check.authorized) {
        return Response.json(
          { error: check.code, message: check.message }, // CONSENT_VIOLATION | CONSENT_REVOKED
          { status: 403 },
        )
      }
    
      return startTraining()
    }
  4. 4. Announce your service to the extension

    One line in the browser lets the ConsentOS extension recognise your site, show the user what you are allowed to do, and refresh the moment a decision lands. The extension checks your page's origin against your registered domain, so another site cannot claim to be you.

    app/layout.tsx (client)
    import { announceService, notifyDecision } from "@consentos/sdk/browser"
    
    announceService({ serviceId: "pixly", name: "Pixly" })
    
    // after your backend receives a decision:
    notifyDecision({ requestId })
  5. 5. Verify receipts

    Receipts are SHA-256 over canonical JSON (sorted keys, no whitespace), signed with Ed25519. Anyone can check them against the public keys; no ConsentOS account needed.

    audit.ts
    const result = await consent.verifyReceipt(receiptId)
    // { valid: true, payloadIntact: true, signatureValid: true,
    //   requestHashValid: true, policyHashValid: true, revoked: false, … }

Endpoints

EndpointAuthPurpose
POST/api/v1/consent/evaluateService keyAsk to use data for a purpose. Returns ALLOW, DENY or REQUIRE_USER.
POST/api/v1/grants/checkService keyRuntime enforcement: is this use permitted right now?
GET/api/v1/consent/requests/:idService keyPoll a REQUIRE_USER request until the user answers.
GET/api/v1/receipts/:idService key or userThe full signed receipt document.
GET/api/v1/receipts/:id/verifyPublicRecompute hashes and check the signature.
POST/api/v1/receipts/verifyPublicVerify a receipt document you hold.
GET/api/v1/keysPublicEd25519 signing keys as a JWK set.
GET/api/v1/services/:idPublicDirectory entry for an integrated service.

Purposes

  • essential

    Storing what a service needs to work at all — your account, your uploads, your settings.

  • analytics

    Measuring how a product is used so it can be improved.

  • personalization

    Using your activity to tailor what you see inside the service.

  • advertising

    Using your data to choose which ads you are shown.

  • foundation_model_training

    Using your content to train general-purpose AI models.

  • third_party_sharing

    Passing your data to other companies.

  • precise_location

    Using your exact location rather than a city or region.

Data types

  • uploaded_images
  • profile
  • account
  • email_address
  • usage_events
  • device_info
  • contacts
  • messages
  • coarse_location
  • precise_location

Anything outside these lists is valid to send, and is escalated to the user rather than guessed at.

Reason codes

Checks run in a fixed order and combine deny-overrides: any deny wins, then any ask, otherwise allow.

POLICY_ALLOWSEvery check passed.
ANONYMIZED_ANALYTICS_ALLOWEDAnalytics on data marked anonymised, under an anonymous-only rule.
PURPOSE_DENIEDThe user blocks this purpose.
RETENTION_EXCEEDS_LIMITretentionDays is above the user's maximum. details carries both numbers.
THIRD_PARTY_SHARING_DENIEDthirdPartySharing is true and the user blocks sharing.
PRECISE_LOCATION_DENIEDPrecise-location data, which the user blocks whatever the purpose.
ANONYMIZATION_REQUIREDAnalytics without anonymized: true under an anonymous-only rule.
PURPOSE_REQUIRES_CONFIRMATIONThe user set this purpose to Ask me. → REQUIRE_USER
RETENTION_UNSPECIFIEDNo retentionDays on a non-essential request. → REQUIRE_USER
UNKNOWN_PURPOSE / UNKNOWN_DATA_TYPENot in the catalogue, so the user decides. → REQUIRE_USER