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.
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
https://api-next.expressconsent.comEvery 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.
X-API-Key: key_8f3c1e2a6b0d4f19.d41d8cd98f00b204e9800998ecf8427eCreate 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.
A key grants full read access to every record your organization holds, and there is no way to scope one down. The API sends no CORS headers, so a browser request fails anyway, but the reason not to do it is the key rather than the CORS error. Call this API from your server.
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:
{
"ok": true,
"data": { "domains": ["example.com"] },
"requestId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}Failure:
{
"ok": false,
"error": {
"code": "CDR_INVALID",
"message": "This CDR has been marked as invalid and cannot be shared",
"requestId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}
}okbooleanrequiredtrueon success,falseon failure. The only reliable success test.dataobjectrequiredThe endpoint's payload, on success only. Its shape is documented per endpoint.
error.codestringrequiredA stable machine-readable code, on failure only. Branch on this rather than on
message. Every code is on API errors.error.messagestringrequiredA human-readable explanation. Written for your logs, not for your users, and not stable. Do not parse it.
error.detailsobjectoptionalStructured detail for the errors that have any. Two do: a rejected
acceptanceobject carriesissues[], and a record that failed acceptance criteria carriesfailures[]alongside anoutcomeofrejected.requestIdstringrequiredA unique identifier for the request. Top-level on success, but nested as
error.requestIdon failure, so readingbody.requestIdgives 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.
pageSizeintegeroptionaldefault20How 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.pageTokenstringoptionalThe
nextPageTokenfrom the previous response. A token that does not correspond to a record on this domain is a400 INVALID_ARGUMENT.orderstringoptionaldefaultdescSort 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.
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.