API conventions

A server-to-server JSON API for reading records, sharing them, and receiving records shared with you. Everything here applies to every endpoint.

bash
curl -sS \
  -H "X-API-Key: $EC_API_KEY" \
  "https://api-next.expressconsent.com/v1/domains"

The capture itself is not part of this API; records are created by the browser SDK. See captureCDR().

Base URL

text
https://api-next.expressconsent.com

Every path begins /v1/. Trailing slashes are ignored, and a path parameter is a single segment: a record identifier containing a / will not match.

Authentication

Every endpoint takes an API key in the X-API-Key header. The value is the key identifier and the secret joined by a single dot, exactly as the dashboard gives it to you. A value with any other number of dots is rejected as malformed.

bash
X-API-Key: key_8f3c1e2a6b0d4f19.d41d8cd98f00b204e9800998ecf8427e

Create keys under Organization → API keys. The secret is shown once, at creation, and is stored only as a hash. If you lose it, issue a new key and revoke the old one.

A key identifies an organization, and that is the only thing it identifies. Every request is scoped to the organization that owns the key: you can read records captured on your own domains and records shared into your account, and nothing else.

One route is exempt. GET /v1/shares/:token is the browser-openable form of a share URL and answers 302 to the dashboard without reading a key and without a JSON envelope, so that a share URL pasted into an email is a working link for a person. Receiving a shared record programmatically is a POST to the same path, and that does take a key.

Revoking a key, or having API access switched off, takes effect within five minutes rather than immediately. Treat a leaked secret as live for that long.

Response envelope

Every JSON response is wrapped, which is every response except the share redirect above. Read ok to tell success from failure, and do not rely on the HTTP status alone, because one error is returned with a 2xx status.

Success:

json
{
  "ok": true,
  "data": { "domains": ["example.com"] },
  "requestId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}

Failure:

json
{
  "ok": false,
  "error": {
    "code": "CDR_INVALID",
    "message": "This CDR has been marked as invalid and cannot be shared",
    "requestId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
  }
}
okbooleanrequired

true on success, false on failure. The only reliable success test.

dataobjectrequired

The endpoint's payload, on success only. Its shape is documented per endpoint.

error.codestringrequired

A stable machine-readable code, on failure only. Branch on this rather than on message. Every code is on API errors.

error.messagestringrequired

A human-readable explanation. Written for your logs, not for your users, and not stable. Do not parse it.

error.detailsobjectoptional

Structured detail for the errors that have any. Two do: a rejected acceptance object carries issues[], and a record that failed acceptance criteria carries failures[] alongside an outcome of rejected.

requestIdstringrequired

A unique identifier for the request. Top-level on success, but nested as error.requestId on failure, so reading body.requestId gives you nothing on exactly the responses you care about. Log it either way. It is the fastest way for us to find a specific request when you report a problem.

Pagination

Only listing the records on a domain paginates. Request a page size, then follow the token.

pageSizeintegeroptionaldefault 20

How many records to return.

1 to 100. A fractional value is truncated toward zero. A value outside the range after truncation, or one that is not a number, is replaced by the default rather than rejected.
pageTokenstringoptional

The nextPageToken from the previous response. A token that does not correspond to a record on this domain is a 400 INVALID_ARGUMENT.

orderstringoptionaldefault desc

Sort direction on the record's creation time.

Only the exact string `asc` selects ascending order. Every other value, including a misspelling, means `desc`.

The response carries nextPageToken when more records exist and omits it on the last page. Stop when it is absent rather than when a page comes back short. A page can contain fewer records than you asked for and still not be the last one.

bash
curl -sS \
  -H "X-API-Key: $EC_API_KEY" \
  "https://api-next.expressconsent.com/v1/domains/example.com/cdrs?pageSize=50&pageToken=$TOKEN"

Requests

POST bodies are JSON, and every one is optional: each endpoint that takes a body has defined behavior for an empty one. Bodies are strict where they are validated: an unrecognized field in acceptance criteria is an error rather than being ignored.

Responses are never cached; every one carries Cache-Control: no-store.

An unknown path is a 404, and a known path with the wrong method is a 405 carrying an Allow header that lists the methods it does accept. Neither checks your API key first, so a 404 from a mistyped path tells you nothing about whether your key is valid.

Errors

Failures use the envelope above with a code from a fixed set. The full table, with the HTTP status for each and whether retrying helps, is on API errors.

One status is worth knowing before you write a client: receiving a shared record that has not finished rendering yet returns 202 with ok: false. A client that treats any 2xx status as success will log a success and find no data.