Skip to content
ContentRemovalDeskDocumentation

Get started

Make your first request

Connect a verified agency, link a creator, and follow a real scan from request to findings.

REST API · JSONv0.8.0 · Live CRD integration
On this page

1. Verify your agency and create a key

Create a CRD agency account at /create-agency and complete onboarding and the existing agency KYC process. After approval, the owner can open /agency/api to provision the agency’s API account automatically. No separate pilot approval is required.

Sign in as the agency owner and open /agency/api. Create a key with the permissions your integration needs. Save the complete crd_live_ secret in your server’s secret store when it is shown; it cannot be displayed again.

Use the secret, not the record ID

A key_… value identifies a credential in the dashboard. It cannot authenticate requests. Use the complete one-time secret; create a replacement if it was lost.

2. Confirm your connection

Set CRD_API_KEY in your backend environment. The examples call https://api.crd.systems; to try them against a copy running on your own computer, set CRD_BASE_URL. Call GET /v1/me to check the credential, environment, assigned workspace, and engine configuration.

Use a key with workspaces:read and save the assigned workspace ID. If configuration is unavailable, have CRD restore it before creating resources. A test secret cannot authenticate to the live service.

Choose the enrollment path that matches the creator

Optional aliases and supported profile_urls may be supplied on either enrollment path. A 201 response means the API enrollment was created, not that protection is active. email_sent: true means the email provider accepted the invitation message; it does not prove inbox delivery or acceptance. Invitations expire after seven days. An identical enrollment retry does not resend email; use the existing agency invitation controls for a resend. Use POST /v1/creators/{id}/resend-invitation with Idempotency-Key for pending API invitations.

Creator situationWhat your backend sendsWhat happens next
Already returned by the agency rosterReuse its returned crt_… ID; use PATCH for identity edits.Read eligibility and explicitly activate if needed. No invitation is required.
Existing CRD creator in this agency; assign your external IDPOST with workspace_id, external_id, display_name, and crd_user_id.Links the existing profile; the current mandate and entitlement remain authoritative.
New creator being invitedPOST with workspace_id, external_id, display_name, email, first_name, last_name, and optional locale.CRD sends the existing invitation email. The creator accepts and completes the required portal steps before eligibility.

4. Confirm authorization and activate protection

Read the creator’s authorization, billing model and current rate. Under active_creators, a creator can be authorized but ineligible until the first activation checkout succeeds.

Obtain the owner’s approval of the monthly rate. Send PUT /v1/creators/{id}/protection with status active, expected_price_cents from creator.billing and a stable UUID Idempotency-Key. If the API returns 202 with checkout_url, the owner must finish checkout; read the creator again before displaying protection as active.

For legacy agreements only, activation remains an API dispatch switch and requires eligible=true. Their existing package and scheduled protection remain unchanged.

Copyable enrollment and activation sequence

This Node.js example uses a Read and write key and the API origin supplied for your account. Replace every fictional identity before sending a live request. For an existing agency creator, use its crd_user_id instead of the email/name invitation fields, or reuse the ID returned by the roster.

The agency’s decision to enable protection belongs in your application. This example represents that saved choice with CRD_PROTECTION_REQUESTED. A successful invitation does not activate protection: the code makes a fresh eligibility read and sends the separate active PUT only when both the saved choice and current eligibility allow it.

After the initial enrollment, store its API creator ID. On creator.updated, refresh that ID and repeat only the eligibility/activation decision. This logic runs in your integration; CRD does not automatically activate a creator merely because its authorization was approved.

Node.js · enroll, inspect, and explicitly activatejavascript
const baseUrl = process.env.CRD_BASE_URL;
const key = process.env.CRD_API_KEY;
if (!baseUrl || !key) throw new Error('Set CRD_BASE_URL and CRD_API_KEY');

async function crd(path, method = 'GET', body, extraHeaders = {}) {
  const response = await fetch(`${baseUrl}${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${key}`,
      ...extraHeaders,
      ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
    },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  });
  const result = await response.json();
  if (!response.ok) throw new Error(`${response.status}: ${result.error?.code}`);
  return result;
}

const connection = await crd('/v1/me');
const enrollment = await crd('/v1/creators', 'POST', {
  workspace_id: connection.workspaces[0].id,
  external_id: 'your-stable-creator-001',
  display_name: 'Example Creator',
  email: '[email protected]',
  first_name: 'Example',
  last_name: 'Creator',
  locale: 'en',
  aliases: ['example_creator'],
  profile_urls: ['https://onlyfans.com/example_creator'],
});
// Save enrollment.id and this original payload in your application.
// 201 creates enrollment; a matching repeat may return 200.
// The invitation email comes from enrollment, not activation.

const current = await crd(`/v1/creators/${enrollment.id}`);
// Enrollment is free and does not activate protection.
// After creator.updated, read the creator again; do not repeat enrollment.
const protectionRequested = process.env.CRD_PROTECTION_REQUESTED === 'true';
const billed = current.billing?.model === 'active_creators';
if (!protectionRequested || !current.authorization?.approved || (!billed && !current.eligible)) {
  console.log({ creatorId: current.id, action: 'await_owner_choice_or_creator_requirements' });
} else if (current.protection_status !== 'active') {
  // Show the quote (quantity, unit price and monthly total) to the owner.
  console.log({ quote: current.billing?.activation_quote });
  // Load the owner's saved approval, not a fresh UUID on every retry.
  // Persist { creator_id, idempotency_key, body } before sending the request.
  const saved = process.env.CRD_APPROVED_PROTECTION_OPERATION;
  if (!saved) throw new Error('Save owner approval before activating protection');
  const operation = JSON.parse(saved);
  if (operation.creator_id !== current.id || operation.body?.status !== 'active'
      || !operation.idempotency_key
      || (billed && (!Number.isSafeInteger(operation.body.expected_price_cents)
        || !Number.isSafeInteger(operation.body.expected_quantity)))) {
    throw new Error('Missing approved creator, price, quantity or retry key');
  }
  // Example saved body: { status: 'active', expected_price_cents: 6900, expected_quantity: 1 }.
  // Use values from the approved quote; never hard-code them in production.
  const result = await crd(`/v1/creators/${current.id}/protection`, 'PUT', operation.body,
    { 'Idempotency-Key': operation.idempotency_key });
  console.log(result);
  // 202 / checkout_url means pending, not active. The owner completes checkout.
  // Read the creator again to confirm protection_status === 'active'.
  // On uncertainty, retry this exact saved key and body. Do not start another change.
}
// For several creators, use POST /v1/protection/bulk/quote then PUT /v1/protection/bulk.
// One owner-approved billing operation; each creator remains independently pausable.

5. Request and follow a scan

Send POST /v1/creators/{id}/scans with an empty JSON body and an Idempotency-Key unique to this logical scan. Save the returned scan ID. A 202 response means queued; poll GET /v1/scans/{id} or receive a signed webhook.

After completion, read GET /v1/creators/{id}/cases. Results use that creator’s native CRD leak records, including existing and scheduled-scan findings. Native findings can have scan_id: null; API scan provenance is preserved when available. Register your HTTPS receiver in /agency/api before requesting the scan if you want its events delivered.

6. Keep your integration synchronized

Open Webhooks in /agency/api, register your public HTTPS receiver and save its signing secret. Verify each delivery, store the event durably, return 2xx promptly and refresh the affected resource.

Use creator.updated for creator identity and eligibility changes, scan.completed or scan.failed for API scan outcomes, and case.created, case.updated or case.deleted for synchronized leak records. Recover missed events through GET /v1/events. Impersonation follow-up is available in the agency portal, not the leak-case event stream.

No Google deindexing step is required

A scan or reported case enters CRD’s existing protection workflow. Your application follows its state; it does not need to submit a Google request.

Request & response example

GET/v1/me
curl --request GET 'https://api.crd.systems/v1/me' \
  --header "Authorization: Bearer $CRD_API_KEY"
Use your secret key in the environment variable.
Example response · 200
{
  "partner": {
    "id": "ptn_example",
    "name": "Example Agency",
    "kind": "agency"
  },
  "workspace_id": "wsp_example",
  "key_id": "key_example",
  "scopes": [
    "workspaces:read",
    "creators:read",
    "creators:write"
  ],
  "expires_at": null,
  "environment": "live",
  "workspaces": [
    {
      "id": "wsp_example",
      "name": "Example Agency",
      "external_id": "agency_001"
    }
  ],
  "engine_configured": true
}

Illustrative response. Replace example resource IDs with the IDs returned by your API requests. View endpoint details.