API reference

Server-to-server endpoints for listing, collecting, sharing, and downloading consent evidence.

On this page

Authentication

Generate an API key in the dashboard (Organization → API Keys). The secret is shown only once — if lost, generate a new key.

Send it as X-API-Key in the format <keyId>.<secret>:

bash
X-API-Key: <keyId>.<secret>

Base URL

bash
export EC_API_KEY="<keyId>.<secret>"

# Production:
export EC_API_BASE_URL="https://api-next.expressconsent.com"

All curl examples below use $EC_API_BASE_URL so they work in any environment.

Response format

All responses are JSON with a request ID for debugging.

Success

json
{
  "ok": true,
  "data": { ... },
  "requestId": "2e9f4a2d-..."
}

Error

json
{
  "ok": false,
  "error": {
    "code": "UNAUTHENTICATED",
    "message": "API key required",
    "requestId": "2e9f4a2d-..."
  }
}

Error codes:

  • UNAUTHENTICATED (401) — missing, malformed, or invalid API key
  • FORBIDDEN (403) — authenticated but not authorized
  • INVALID_ARGUMENT (400) — bad query/path input
  • NOT_FOUND (404) — resource doesn’t exist or isn’t accessible
  • METHOD_NOT_ALLOWED (405) — wrong HTTP method for endpoint
  • CONFLICT (409) — conflicting state (e.g. duplicates)
  • GONE (410) — resource expired (e.g. share token past expiry)
  • CDR_INVALID (410) — CDR has been administratively invalidated; treat like NOT_FOUND for evidence purposes
  • FAILED_PRECONDITION (412) — action requires a prior step

Endpoints

Read endpoints

GET /v1/domains

List all domains for your organization.

bash
curl -sS \
  -H "X-API-Key: $EC_API_KEY" \
  "$EC_API_BASE_URL/v1/domains"

GET /v1/domains/:domainId/cdrs

List CDRs for a domain, with pagination and optional metadata filtering.

  • pageSize: 1–100 (default 20)
  • order: asc | desc (default desc)
  • pageToken: CDR ID to start after
  • metadataKey + metadataValue: filter by custom metadata (both required together)
bash
curl -sS \
  -H "X-API-Key: $EC_API_KEY" \
  "$EC_API_BASE_URL/v1/domains/example.com/cdrs?pageSize=20&order=desc"
bash
curl -sS \
  -H "X-API-Key: $EC_API_KEY" \
  "$EC_API_BASE_URL/v1/domains/example.com/cdrs?metadataKey=leadId&metadataValue=123"

Example response

json
{
  "ok": true,
  "data": {
    "cdrs": [
      {
        "cdrId": "abc_123",
        "domainId": "example.com",
        "domain": "example.com",
        "createdAt": 1738600000000,
        "contentType": "image/jpeg",
        "size": 276472,
        "collected": true,
        "downloadUrl": "https://storage.googleapis.com/... (short-lived)",
        "customMetadata": { "leadId": "123" },
        "sessionId": "session_abc",
        "subGroupIds": ["step-1"]
      }
    ],
    "nextPageToken": "abc_122"
  }
}

GET /v1/cdrs/:cdrId

Fetch a single CDR with full detail, including signer telemetry (IP, User-Agent, geo).

For CDRs received via sharing, the response includes guest: true and producerOrgId indicating the original producing organization.

bash
curl -sS \
  -H "X-API-Key: $EC_API_KEY" \
  "$EC_API_BASE_URL/v1/cdrs/abc_123"

Example response

json
{
  "ok": true,
  "data": {
    "cdr": {
      "cdrId": "abc_123",
      "domainId": "example.com",
      "domain": "example.com",
      "organizationName": "Acme Corp",
      "capturedAt": 1738600000000,
      "createdAt": 1738600000000,
      "contentType": "image/jpeg",
      "size": 276472,
      "collected": true,
      "downloadUrl": "https://storage.googleapis.com/...",
      "pageUrl": "https://example.com/consent",
      "signerTelemetry": {
        "ip": "203.0.113.1",
        "ipChain": ["203.0.113.1"],
        "userAgent": "Mozilla/5.0 ...",
        "geo": {
          "countryCode": "US",
          "region": "CA",
          "city": "Los Angeles",
          "latitude": 34.0522,
          "longitude": -118.2437,
          "accuracyRadiusKm": 20,
          "source": "maxmind"
        }
      },
      "customMetadata": { "leadId": "123" },
      "disclosures": [
        {
          "key": "tcpa",
          "language": "By submitting this form, you consent to receive calls via autodialer...",
          "agreed": true
        }
      ],
      "sessionId": "session_abc"
    }
  }
}

Write endpoints

POST /v1/cdrs/:cdrId/collect

Pay for a CDR for your organization. After a successful collect, your org is billed for this CDR and gets full download access. Idempotent for your org — calling twice returns alreadyCollected: true.

Multiple organizations can independently collect the same CDR (each pays their own bill and gets their own access). One org collecting does not unlock access for other orgs.

bash
curl -sS -X POST \
  -H "X-API-Key: $EC_API_KEY" \
  "$EC_API_BASE_URL/v1/cdrs/abc_123/collect"

Example response

json
{
  "ok": true,
  "data": {
    "cdrId": "abc_123",
    "collected": true,
    "alreadyCollected": false
  }
}

POST /v1/cdrs/:cdrId/share

Generate a share URL for a CDR. The returned shareUrl can be given to a business partner (e.g. a lead buyer) so they can receive the evidence. The CDR does not need to be collected first — either you or the recipient can collect (pay for) the CDR at any time. If you have already paid, your recipients get free download access when they POST to the share URL.

Optional body: { "expiresInMs": 3600000 } (default: 30 days, max: 2 years). Each call generates a new unique token.

bash
curl -sS -X POST \
  -H "X-API-Key: $EC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expiresInMs":3600000}' \
  "$EC_API_BASE_URL/v1/cdrs/abc_123/share"

Example response

json
{
  "ok": true,
  "data": {
    "token": "share_token_xyz",
    "shareUrl": "/v1/shares/share_token_xyz",
    "expiresAt": 1739200000000
  }
}

POST /v1/shares/:token

Open a share URL with your org’s API key. By default this is a single-step collect: the CDR is added to your org and your org is billed for it. If the org that created the share has already paid for the CDR, you are not billed — free download access is granted instead.

When using autoShare, the SDK returns a full absolute shareUrl — the recipient POSTs to that URL with their API key. When using the server-side share endpoint, prepend your API base URL to the returned path. The legacy path /v1/shares/:token/claim remains supported for backward compatibility and behaves identically.

Idempotent — POSTing the same URL twice from the same org returns alreadyClaimed: true on the second call and never bills twice. You cannot open a share URL you generated yourself (returns 400 INVALID_ARGUMENT). Expired tokens return 410 GONE.

bash
curl -sS -X POST \
  -H "X-API-Key: $EC_API_KEY" \
  "$EC_API_BASE_URL/v1/shares/share_token_xyz"

Example response

json
{
  "ok": true,
  "data": {
    "claimed": true,
    "alreadyClaimed": false,
    "cdrId": "abc_123",
    "domainId": "example.com",
    "shareEventId": "se_abc123def456"
  }
}

Save without paying (opt-out)

To save the CDR to your org without billing yourself, pass { "collect": false }. The CDR shows up in your account but you cannot download it until you collect later via POST /v1/cdrs/:cdrId/collect. If the org that created the share has already paid, you receive free download access regardless of collect: false — the sharer wanted you to have it.

bash
# Save the share to your org without billing yourself.
# Use when you only want visibility (no download access yet).
curl -sS -X POST \
  -H "X-API-Key: $EC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"collect": false}' \
  "$EC_API_BASE_URL/v1/shares/share_token_xyz"

Collected vs Pending

The collected field on every CDR response is per-organization: it reflects whether your org has download access to this CDR. Two orgs viewing the same CDR can see different collected values.

Your org has collected: true for a CDR when either of these is true:

  • Your org paid for it — via auto-collect at capture time, or by POSTing to a share URL (which collects by default), or via POST /v1/cdrs/:cdrId/collect.
  • Your org received it via a share URL from an org that has already paid — you get free download access automatically (no billing). See POST /v1/shares/:token.

Notes:

  • downloadUrl is only returned when collected === true for your org.
  • Download URLs are short-lived signed URLs (~5 min). Store cdrId, not the URL.
  • A single payment from one org enables that org to share with any number of partners, and every partner gets free download access. Free access flows one hop forward; if partners then re-share to their downstream partners, those downstream recipients collect (pay) on their own POST.

Package CDR API Coming soon

Package CDRs bundle multiple CDRs from the same user session into a composite evidence document. The following endpoints will enable programmatic access to Package CDRs, including sharing entire multi-step consent flows in a single operation.

  • GET /v1/packages/:packageId — Get Package CDR details (all CDRs, session context, telemetry)
  • POST /v1/packages/:packageId/share — Share a Package CDR (generate a share link for the entire session)
  • POST /v1/package-shares/:token — Open a Package CDR share URL (single-step collect for the whole bundle)

Until these endpoints are available, share individual CDRs using POST /v1/cdrs/:cdrId/share. When a buyer receives multiple CDRs from the same session, they appear together in the buyer’s Package CDRs view. See the Package CDRs documentation for more details.

Related docs