API reference
Server-to-server endpoints for listing, collecting, sharing, and downloading consent evidence.
On this page
API keys must never appear in browser/client-side code. Treat them like passwords: store as environment secrets on your server.
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>:
X-API-Key: <keyId>.<secret>Base URL
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
{
"ok": true,
"data": { ... },
"requestId": "2e9f4a2d-..."
}Error
{
"ok": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "API key required",
"requestId": "2e9f4a2d-..."
}
}Error codes:
UNAUTHENTICATED(401) — missing, malformed, or invalid API keyFORBIDDEN(403) — authenticated but not authorizedINVALID_ARGUMENT(400) — bad query/path inputNOT_FOUND(404) — resource doesn’t exist or isn’t accessibleMETHOD_NOT_ALLOWED(405) — wrong HTTP method for endpointCONFLICT(409) — conflicting state (e.g. duplicates)GONE(410) — resource expired (e.g. share token past expiry)FAILED_PRECONDITION(412) — action requires a prior step
Endpoints
Read endpoints
GET /v1/domains
List all domains for your organization.
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(defaultdesc)pageToken: CDR ID to start aftermetadataKey+metadataValue: filter by custom metadata (both required together)
curl -sS \
-H "X-API-Key: $EC_API_KEY" \
"$EC_API_BASE_URL/v1/domains/example.com/cdrs?pageSize=20&order=desc"curl -sS \
-H "X-API-Key: $EC_API_KEY" \
"$EC_API_BASE_URL/v1/domains/example.com/cdrs?metadataKey=leadId&metadataValue=123"When the SDK’s initial upload fails, it retries automatically on the user’s next page load. Use metadata filtering to find CDRs that uploaded via retry for lead records missing a CDR ID. Pass a unique identifier in captureCDR({ custom: { uid: '...' } }), then query with metadataKey=uid&metadataValue=... to find the CDR. See Offline resilience for the full reconciliation pattern.
Example response
{
"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.
curl -sS \
-H "X-API-Key: $EC_API_KEY" \
"$EC_API_BASE_URL/v1/cdrs/abc_123"Example response
{
"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
Mark a CDR as Collected (billing attribution). Idempotent — calling twice returns alreadyCollected: true.
curl -sS -X POST \
-H "X-API-Key: $EC_API_KEY" \
"$EC_API_BASE_URL/v1/cdrs/abc_123/collect"Example response
{
"ok": true,
"data": {
"cdrId": "abc_123",
"collected": true,
"alreadyCollected": false
}
}POST /v1/cdrs/:cdrId/share
Generate a share token for a CDR. The returned shareUrl can be given to a business partner (e.g. a lead buyer) so they can claim the evidence. The CDR does not need to be collected first — either the sharing party or the claiming party can collect (pay for) the CDR at any time.
Optional body: { "expiresInMs": 3600000 } (default: 30 days, max: 2 years). Each call generates a new unique token.
If you’re generating leads from a website, use captureCDR({ autoShare: true }) instead — it returns the share URL in the same call with no extra API request. This endpoint is for server-side share generation when you need to share after the fact.
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
{
"ok": true,
"data": {
"token": "share_token_xyz",
"shareUrl": "/v1/shares/share_token_xyz",
"expiresAt": 1739200000000
}
}POST /v1/shares/:token
Claim a shared CDR into your organization. Once claimed, the evidence appears under your account and you can download it. Once any party has collected a CDR, other parties with access do not need to pay again.
When using autoShare, the SDK returns a full absolute shareUrl — the lead buyer POSTs to that URL with their API key. When using this endpoint to create shares server-side, prepend your API base URL to the returned path. Legacy /v1/shares/:token/claim remains supported for backward compatibility.
Idempotent — claiming the same token twice returns alreadyClaimed: true. You cannot claim your own shared CDR (returns 400 INVALID_ARGUMENT). Expired tokens return 410 GONE.
curl -sS -X POST \
-H "X-API-Key: $EC_API_KEY" \
"$EC_API_BASE_URL/v1/shares/share_token_xyz"Example response
{
"ok": true,
"data": {
"claimed": true,
"alreadyClaimed": false,
"cdrId": "abc_123",
"domainId": "example.com",
"shareEventId": "se_abc123def456"
}
}Collected vs Pending
A CDR can exist in two states. Pending means evidence is saved but billing attribution hasn’t been recorded. Collected means billing attribution exists.
downloadUrlis only returned whencollected === true.- Download URLs are short-lived signed URLs (~5 min). Store
cdrId, not the URL.
By default, new organizations have auto-collect enabled — CDRs are automatically collected (billing attributed) as soon as they are created. If you need manual control over when billing occurs (e.g. to let the lead buyer pay), contact support to disable auto-collect for your organization.
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— Claim a shared Package CDR
Until these endpoints are available, share individual CDRs using POST /v1/cdrs/:cdrId/share. When a buyer claims 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
- Sharing evidence with lead buyers — autoShare, claiming, and the full lead generator / lead buyer flow
- Webhooks — receive real-time notifications when CDRs are rendered
- Quickstart
- Troubleshooting