Redpoint CDP™ Documentation
Auto Light Dark
Auto Light Dark

RTBF onboarding

Overview

This topic provides the right to be forgotten (RTBF) scenarios to run and the results to expect. For the request/response contract (fields, validation rules, error codes) refer to the RTBF API reference.

What Redpoint provides at onboarding

You need the following before you can test. Redpoint supplies all of it; you supply your egress IP addresses if asked.

Item

Value

Notes

Test-environment base URL

<TEST_BASE_URL> (provided in the form of https://<env>-<infra-id>-rtbf.redpointcdp.com)

All calls go to {BASE}/api-op/v1/rtbf/requests. This is your test environment, separate from production.

OAuth2 client_id

<OAUTH_CLIENT_ID>

Your application's identity in Redpoint's Microsoft Entra tenant. Identifies who is calling. Not the same value as the id inside AUDIENCE.

OAuth2 Client secret or certificate

<OAUTH_CLIENT_SECRET>

(provided separately)

Delivered to you separately, through a secure channel. Treat as a production credential, even in test. Rotation is done in Entra and needs nothing from you on the API side.

Entra tenant id (TENANT_ID)

<ENTRA_TENANT_ID>

Redpoint's Microsoft Entra directory id. Used only in the token endpoint URL, and the same for each Redpoint environment. Not your Data Readiness Hub tenant id (below).

Scope / audience (AUDIENCE)

<AUDIENCE>

The RTBF API's identifier, provided as an Application ID URI of the form api://<id>. Request the scope <AUDIENCE>/.default. The <id> is a different value from your client_id: client_id identifies your application (who is calling); the audience identifies our API (what the token is for).

Your Data Readiness Hub test Client id(s)

<DRH_CLIENT_ID>

Sent as X-ClientId. Required only if you are authorized for more than one tenant. Unrelated to the Entra ENTRA_TENANT_ID above.

Egress IP allow-listing

(you supply)

If the environment's WAF uses an IP allow-list, give Redpoint the egress IPs your test traffic will come from; otherwise calls are refused with 403 before they’re authenticated.

Test-data rules: read before your first POST

Erasure is irreversible, even in the test environment. A completed erasure request deletes all records matching the identifiers you submit. Only test against the designated test tenant, and only with synthetic subjects; never submit a real person's identifiers. Use obviously fake values such as rtbf-test-001@example.com.

  • Multiple identifiers in one request are OR'd (union): all records matching any of them is erased. Keep each test request to one synthetic subject.

  • Use a fresh, randomly generated lowercase UUID v4 as subject_request_id for each new test case (it is your idempotency key; reusing one deliberately is itself a test, see Scenario B).

Before you start: mint a token

Standard OAuth2 client-credentials authenticate against Microsoft Entra ID. Cache the token until shortly before expires_in and reuse it; do not mint one per request.

Bash
TOKEN=$(curl -sS -X POST "https://login.microsoftonline.com/<ENTRA_TENANT_ID>/oauth2/v2.0/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=<OAUTH_CLIENT_ID>" \
  -d "client_secret=<OAUTH_CLIENT_SECRET>" \
  -d "scope=<AUDIENCE>/.default" | jq -r .access_token)

BASE="<TEST_BASE_URL>/api-op/v1/rtbf/requests"
AUTH="Authorization: Bearer $TOKEN"
DRH_CLIENT_ID="X-ClientId: <DRH_CLIENT_ID>"
JSON="Content-Type: application/json"

The <…> values above are provided by Redpoint during onboarding. If the token call fails, the problem is on the Entra side (wrong client_id/secret, wrong tenant, wrong scope); subsequent calls will not work until it succeeds.

First: Verify your access

Before submitting anything, confirm your credential and tenant authorization are wired up end to end. GET /self-check runs the identical authentication as a real call but creates no request and changes no data; so unlike a POST (which starts a real, irreversible erasure), it is safe to call first, and as often as you like. This is the recommended first call for any new integration.

Bash
# Uses $AUTH from the token step above. /self-check is a sibling of /requests.
curl -sS -i "<TEST_BASE_URL>/api-op/v1/rtbf/self-check" -H "$AUTH"
# add  -H "$TENANT"  only if you are authorized for more than one tenant.
#
# EXPECT: 200, echoing the context we resolved for your call:
# {
#   "client_id":        "<the Data Readiness Hub tenant we resolved you to>",
#   "app_id":           "<your application id>",
#   "roles_satisfied":  true,
#   "origin_verified":  true,
#   "token_expires_at": "<UTC timestamp>"
# }
#
# It creates no request and no data change. When X-ClientId is omitted, client_id
# confirms which tenant we resolved you to. A 401 or 403 here means the same as it
# would on a real request (see Scenario C) and carries the same
# X-RedPoint-CorrelationId -- fix it here, before you submit anything.

Scenario A: Submit, poll, cancel (happy path)

Bash
ID=$(python3 -c "import uuid; print(uuid.uuid4())")

# A1. Submit an erasure request for one synthetic subject
curl -sS -i -X POST "$BASE" -H "$AUTH" -H "$DRH_CLIENT_ID" -H "$JSON" -d "{
  \"subject_request_id\": \"$ID\",
  \"subject_request_type\": \"erasure\",
  \"subject_identities\": [
    { \"identity_type\": \"email\", \"identity_value\": \"rtbf-test-001@example.com\", \"identity_format\": \"raw\" }
  ],
  \"submitted_time\": \"2026-09-08T12:00:00Z\"
}"
# EXPECT: 201 Created, a Location header ending in /rtbf/requests/$ID,
#         body: request_status "pending", received_time set,
#         expected_completion_time = submitted_time + 30 days.
#         subject_identities is NOT echoed back (by design).

# A2. Poll its status
curl -sS "$BASE/$ID" -H "$AUTH" -H "$DRH_CLIENT_ID"
# EXPECT: 200, request_status "pending"

# A3. Cancel it while still pending
curl -sS -i -X DELETE "$BASE/$ID" -H "$AUTH" -H "$DRH_CLIENT_ID"
# EXPECT: 202 Accepted, body request_status "cancelled"

# A4. Confirm the cancel persisted
curl -sS "$BASE/$ID" -H "$AUTH" -H "$DRH_CLIENT_ID"
# EXPECT: 200, request_status "cancelled"   (final — it never changes again)

# A5. Cancel again
curl -sS -i -X DELETE "$BASE/$ID" -H "$AUTH" -H "$DRH_CLIENT_ID"
# EXPECT: 409, error.message "request cannot be cancelled in status: cancelled"

Scenario B: Idempotency

Bash
ID=$(python3 -c "import uuid; print(uuid.uuid4())")
BODY="{ \"subject_request_id\": \"$ID\", \"subject_request_type\": \"erasure\",
        \"subject_identities\": [ { \"identity_type\": \"email\", \"identity_value\": \"rtbf-test-002@example.com\", \"identity_format\": \"raw\" } ],
        \"submitted_time\": \"2026-09-08T12:00:00Z\" }"

# B1. Submit
curl -sS -i -X POST "$BASE" -H "$AUTH" -H "$DRH_CLIENT_ID" -H "$JSON" -d "$BODY"
# EXPECT: 201

# B2. Re-send the IDENTICAL body (simulates a retry after a dropped response)
curl -sS -i -X POST "$BASE" -H "$AUTH" -H "$DRH_CLIENT_ID" -H "$JSON" -d "$BODY"
# EXPECT: 201 again, returning the SAME request — nothing new was created.
#         Your connector must treat this as success, not as a duplicate error.

# B3. Re-use the same id for a DIFFERENT subject
curl -sS -i -X POST "$BASE" -H "$AUTH" -H "$DRH_CLIENT_ID" -H "$JSON" -d "{
  \"subject_request_id\": \"$ID\", \"subject_request_type\": \"erasure\",
  \"subject_identities\": [ { \"identity_type\": \"email\", \"identity_value\": \"rtbf-test-999@example.com\", \"identity_format\": \"raw\" } ],
  \"submitted_time\": \"2026-09-08T12:00:00Z\" }"
# EXPECT: 400 — the id is already bound to a different request. Mint a fresh id and resubmit.

Scenario C: Authentication and validation errors

Each error carries the OpenDSR §7.6 object { "error": { "code": <http status>, "message": "…", "correlation_id": "…" } }. Confirm your connector reads error.code/error.message rather than assuming a plain-text body, and that it records error.correlation_id; see C7 in the code example below, which affects how supportable your integration is in production.

Bash
# C1. No token at all
curl -sS -i -X POST "$BASE" -H "$DRH_CLIENT_ID" -H "$JSON" -d "$BODY"
# EXPECT: 401, "Authorization: Bearer token is required"
#         (the one credential error that tells you exactly what is wrong)

# C2. A bad or expired token
curl -sS -i -X POST "$BASE" -H "Authorization: Bearer not-a-real-token" -H "$DRH_CLIENT_ID" -H "$JSON" -d "$BODY"
# EXPECT: 403, "invalid token or tenant not granted"

# C3. A tenant you are NOT authorized for
curl -sS -i -X POST "$BASE" -H "$AUTH" -H "X-ClientId: some-other-tenant" -H "$JSON" -d "$BODY"
# EXPECT: 403, the SAME message as C2 — deliberately. The service never reveals
#         which tenants or applications exist, or how close a guess was.

# C4. Malformed body (not valid JSON)
curl -sS -i -X POST "$BASE" -H "$AUTH" -H "$DRH_CLIENT_ID" -H "$JSON" -d '{ not json'
# EXPECT: 400 with the §7.6 error object

# C5. Bad subject_request_id (uppercase / not a v4 UUID)
curl -sS -i -X POST "$BASE" -H "$AUTH" -H "$DRH_CLIENT_ID" -H "$JSON" -d '{
  "subject_request_id": "NOT-A-UUID", "subject_request_type": "erasure",
  "subject_identities": [ { "identity_type": "email", "identity_value": "x@example.com" } ],
  "submitted_time": "2026-09-08T12:00:00Z" }'
# EXPECT: 400

# C6. Unknown request id
curl -sS -i "$BASE/$(python3 -c 'import uuid; print(uuid.uuid4())')" -H "$AUTH" -H "$DRH_CLIENT_ID"
# EXPECT: 404

# C7. Capture the correlation id from a failure. Do this for EVERY non-2xx.
curl -sS -i -X POST "$BASE" -H "Authorization: Bearer not-a-real-token" -H "$DRH_CLIENT_ID" -H "$JSON" -d "$BODY" \
  | grep -i '^X-RedPoint-CorrelationId:'
# EXPECT: exactly one header carrying a random UUID v4, identical to
#         error.correlation_id in the body. A different value on every call.
#
#         Record it in YOUR logs alongside the failed request. Because C2 and C3
#         are indistinguishable to you, this id is the only way Redpoint can tell
#         you which cause actually fired — and it cannot be recovered later if
#         you did not keep it.

On the uniform 403. Scenarios C2 and C3 return an identical response by design. If you receive a 403 during real use: first check that your token is unexpired and was requested for the correct AUDIENCE; then confirm with Redpoint that your application's authorization for the tenant is active and that your egress IPs are permitted. The service will not tell you which of these it was, but the correlation_id on that response lets Redpoint tell you. Quote it, with the approximate time and the endpoint you called, and support can name the exact cause from the server-side log. Report it promptly: those log entries are retained for a limited window.

What to expect about status progression

  • A request does not complete instantly. Requests are batched: arrival schedules a future processing window, and processing runs only when the tenant's other workloads are idle. A request will sit at pending for some time after submission; this is normal.

  • The transitions pending → in_progress → completed are driven by Redpoint's processing, not by anything you call. There is no consumer endpoint to advance a request, and the public API exposes status only, not the underlying processing history.

  • In the test environment, Redpoint will run or simulate processing on request so you can observe a full lifecycle. To see your test request reach completed, coordinate with your Redpoint contact and tell them the subject_request_id.

  • Once a request is in_progress, it can no longer be cancelled (DELETE → 409); completed and cancelled are final. There is no failed status; a request that cannot be completed on one pass is retried on the next window rather than surfaced to you as a failure.

  • Poll GET at a sensible interval (minutes, not seconds). expected_completion_time is your statutory reference point and is anchored on the submitted_time you sent, not on when Redpoint accepted the call.

Certification checklist

Your integration is ready for a production credential when each item below has been demonstrated against the test environment:

  • Verify access: GET /self-check returns 200 before any submission; verifies access without creating a request.
  • Scenario A, A1–A2: submit a request and read it back as pending (201 / 200).
  • Scenario A, A3–A5: cancel while pending (202), confirm cancelled, and handle the repeat-cancel 409 without alarming.
  • Scenario B, B2: treat an identical-replay 201 as success, not as a duplicate error.
  • Scenario B, B3: on a reused-id 400, mint a fresh subject_request_id rather than retrying.
  • Scenario C, C1–C3: distinguish 401 (fix the header) from 403 (check token audience, then contact Redpoint).
  • Scenario C, C4–C6: parse the §7.6 error object on each non-2xx.
  • Scenario C, C7: capture error.correlation_id (or the X-RedPoint-CorrelationId header) on each non-2xx and write it to your own logs; it is what Redpoint needs to diagnose a 403, and it cannot be recovered after the fact.
  • Token caching: reuse a token across calls until near expires_in; do not mint per request.
  • Observed one request reach completed (with Redpoint driving processing) and confirmed your connector reports it as fulfilled.
  • Back off and retry on 503 + Retry-After rather than failing the request.

Related: RTBF API reference (the contract this procedure exercises). Questions about your credential, tenant authorization, or a test request's progress should be directed to your Redpoint onboarding contact.

Last updated: