DSAR, preferences, ROPA & vendors
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
DSAR, preferences, ROPA & vendors
The privacy/governance surface covers everything a DPO needs outside of the consent-decision log itself: Data Subject Access Requests (DSAR), a preference-center record store for marketing-style opt-ins, Records of Processing Activities (RoPA), and third-party vendor risk tracking. Every route requires an API key and is scoped to the calling key's org, so one org can never see another's DSARs, vendors, or RoPA entries.
Resource | Read scope | Write scope |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
A legacy key with no scopes list has full access to all four; scoping is opt-in (see API keys, members, webhooks & usage).
DSAR — Data Subject Access Requests
DSARs move through a fixed, one-way status graph: received → verifying → in_progress → completed, with rejected reachable from any non-terminal state. There's no going backward and no skipping steps.
List all DSARs
GET /v1/dsarScope: dsar:read. Returns every request in the org across all statuses (there's no per-status filter on this endpoint).
curl -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
https://api.cookiemunch.net/v1/dsar[
{
"id": "dsar_7c1b",
"type": "access",
"subjectEmail": "[email protected]",
"regulation": "gdpr",
"status": "received",
"createdAt": 1730332800000,
"dueAt": 1732924800000
}
]dueAt is computed at creation from the regulation's statutory window: 30 days for gdpr, 45 days for ccpa.
const requests = await client.dsar.list();Create a DSAR
POST /v1/dsarScope: dsar:write
Field | Type | Required | Description |
|---|---|---|---|
|
| yes | The right being exercised. |
| string | yes | The data subject's email. |
|
| yes | Governing regulation; sets |
| string | no | Free-text intake note. |
curl -X POST https://api.cookiemunch.net/v1/dsar \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "type": "access", "subjectEmail": "[email protected]", "regulation": "gdpr" }'201 Created:
{
"request": {
"id": "dsar_7c1b",
"type": "access",
"subjectEmail": "[email protected]",
"regulation": "gdpr",
"status": "received",
"createdAt": 1730332800000,
"dueAt": 1732924800000
}
}Fires a dsar.created webhook. Errors: 400 { "error": "invalid dsar intake: <reason>" } for a missing/malformed type, subjectEmail, or regulation.
const { request } = await client.dsar.create({
type: 'access',
subjectEmail: '[email protected]',
regulation: 'gdpr',
});Advance a DSAR
POST /v1/dsar/{id}/advanceScope: dsar:write
Field | Type | Required | Description |
|---|---|---|---|
|
| yes | Target status. |
curl -X POST https://api.cookiemunch.net/v1/dsar/dsar_7c1b/advance \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "toStatus": "verifying" }'{ "request": { "id": "dsar_7c1b", "status": "verifying", "...": "..." } }Fires a dsar.updated webhook. Errors: 400 { "error": "invalid toStatus" } if it isn't one of the five known statuses. 404 if id doesn't exist. 409 { "error": "illegal transition: received -> completed" } if you try to skip a step.
const { request } = await client.dsar.advance('dsar_7c1b', 'verifying');Preference center
A lightweight, identity-keyed store of per-subject processing preferences — newsletter, SMS, profiling, etc. — separate from the cookie-consent ledger. Records support single- or double-opt-in; saving via this endpoint always uses single-opt-in, which is confirmed immediately (confirmed: true).
List preference records
GET /v1/preferencesScope: consent:read
curl -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
https://api.cookiemunch.net/v1/preferences[
{
"subjectId": "[email protected]",
"purposes": { "newsletter": true, "sms": false },
"method": "single-opt-in",
"confirmed": true,
"updatedAt": 1719858421000,
"version": 1
}
]Errors: 501 { "error": "preference center not configured" } on a self-host without a preference store wired up.
const records = await client.preferences.list();Save a preference record
POST /v1/preferencesScope: consent:write
Field | Type | Required | Description |
|---|---|---|---|
| string | yes | End-user identifier — email, internal id, etc. |
| object | yes | Map of purpose name → boolean. Merged into any existing record; each write bumps |
curl -X POST https://api.cookiemunch.net/v1/preferences \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "subjectId": "[email protected]", "purposes": { "newsletter": true, "sms": false } }'{
"record": {
"subjectId": "[email protected]",
"purposes": { "newsletter": true, "sms": false },
"method": "single-opt-in",
"confirmed": true,
"updatedAt": 1719858421000,
"version": 1
}
}Errors: 400 { "error": "subjectId required" } / { "error": "purposes required" }. 501 if the preference store isn't configured.
const { record } = await client.preferences.save('[email protected]', { newsletter: true, sms: false });Vendors (processors)
Registering a vendor computes a transparent, deterministic 0–100 risk score: +40 with no DPA signed, +25 for sharing special-category data, up to +15 for subprocessor depth, +20 for a non-adequacy-decision region, minus up to 25 credit for ISO27001/SOC2 certifications. score >= 60 is high, >= 30 is medium, else low.
List vendors
GET /v1/vendorsScope: vendors:read
curl -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
https://api.cookiemunch.net/v1/vendors[
{
"id": "vnd_1",
"name": "Segment",
"category": "analytics",
"dataShared": ["device_id", "email_hash"],
"dpaSigned": true,
"subprocessors": 3,
"certifications": ["SOC2"],
"region": "US",
"risk": { "score": 34, "band": "medium" }
}
]const vendors = await client.vendors.list();Create a vendor
POST /v1/vendorsScope: vendors:write
Field | Type | Required | Description |
|---|---|---|---|
| string | yes | Vendor name. |
| string | yes | e.g. |
| string[] | yes | Data categories shared with the vendor. |
| boolean | yes | Whether a Data Processing Agreement is signed. |
| number | yes | Count of the vendor's own subprocessors. |
| string[] | yes | e.g. |
| string | yes | Primary processing region, e.g. |
curl -X POST https://api.cookiemunch.net/v1/vendors \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Segment", "category": "analytics", "dataShared": ["device_id"],
"dpaSigned": true, "subprocessors": 3, "certifications": ["SOC2"], "region": "US"
}'201 Created: { "vendor": { "id": "vnd_1", "...": "..." }, "risk": { "score": 34, "band": "medium" } }.
Errors: 400 { "error": "<message>" } if a required field is missing or the wrong type.
const { vendor, risk } = await client.vendors.create({
name: 'Segment', category: 'analytics', dataShared: ['device_id'],
dpaSigned: true, subprocessors: 3, certifications: ['SOC2'], region: 'US',
});Records of Processing Activities (RoPA)
List RoPA entries
GET /v1/ropaScope: ropa:read
curl -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
https://api.cookiemunch.net/v1/ropa[
{
"id": "ropa_1",
"name": "Email marketing",
"purpose": "Send product updates",
"legalBasis": "consent",
"dataCategories": ["email"],
"recipients": ["Mailchimp"],
"retentionDays": 730,
"crossBorderTransfer": true
}
]const entries = await client.ropa.list();Create a RoPA entry
POST /v1/ropaScope: ropa:write
Field | Type | Required | Description |
|---|---|---|---|
| string | yes | Activity name. |
| string | yes | Purpose of processing. |
|
| yes | GDPR Art. 6 legal basis. |
| string[] | yes | Personal-data categories processed. |
| string[] | yes | Who receives the data. |
| number | yes | Retention period, in days. |
| boolean | yes | Whether data crosses borders. |
curl -X POST https://api.cookiemunch.net/v1/ropa \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Email marketing", "purpose": "Send product updates",
"legalBasis": "consent", "dataCategories": ["email"],
"recipients": ["Mailchimp"], "retentionDays": 730, "crossBorderTransfer": true
}'201 Created: { "entry": { "id": "ropa_1", "name": "Email marketing", "...": "..." } }.
Errors: 400 { "error": "invalid RoPA entry: <reason>" } for a missing/malformed field.
const { entry } = await client.ropa.create({
name: 'Email marketing', purpose: 'Send product updates', legalBasis: 'consent',
dataCategories: ['email'], recipients: ['Mailchimp'], retentionDays: 730,
crossBorderTransfer: true,
});Next: API keys, members, webhooks & usage for how dsar:*/vendors:*/ropa:* scopes are issued, or cookies, scan & verify for the consent-adjacent site data this page doesn't cover.