Endpoints and field mapping
Understand the four connector routes, query criteria, mapping direction, and response schemas.
@askdepth/audience-contractv0.1.2On this page
Every route requires a valid signature, including health and schema. Paths below are relative to the configured base path, normally /askdepth/v1.
| Method | Path | Response |
|---|---|---|
| GET | /health | { ok, contractVersion, capabilities }. |
| GET | /schema | { columns: [{ name, type }] }; no sample values or candidate rows. |
| POST | /candidates/count | { count }, a nonnegative integer. |
| POST | /candidates/search | { rows, nextCursor? }. |
Count/search bodies use application/json. Do not put audience criteria or IDs into a query string. There are no write endpoints.
Health and capabilities
{
"ok": true,
"contractVersion": "1.0.0",
"capabilities": ["externalIdIn", "attributeFilters", "dateRanges", "randomSample"]
}contractVersion is the wire version, independent of the package version 0.1.2. Advertise only behavior the connector can honor.
| Flag | Meaning |
|---|---|
externalIdIn | Filter an explicit list of canonical external IDs. |
attributeFilters | Filter declared attr.* fields. |
dateRanges | Filter signupAt with a datetime range. |
randomSample | Produce a random sample rather than the first N rows. |
declaredSchema | Schema comes from configuration, as in the REST adapter. |
Mapping has two directions
Connector configuration uses canonical field → source column:
const fieldMapping = {
externalId: 'id',
email: 'email_address',
segment: 'plan',
};The request body's mapping uses source column → canonical field:
{
"mapping": {
"id": "externalId",
"email_address": "email",
"plan": "segment"
}
}The configured allowlist remains authoritative. Inverting request mapping often breaks only differently named fields, while identically named fields appear to work. A green health/schema check followed by malformed_request on count/search is a reason to inspect the direction first.
Search and count bodies
{
"criteria": {
"all": [
{ "field": "segment", "op": "in", "values": ["team", "business"] },
{ "field": "isActive", "op": "eq", "value": true }
],
"suppressExternalIds": ["user_opted_out"]
},
"mapping": {
"id": "externalId",
"email_address": "email",
"plan": "segment",
"is_active": "isActive"
},
"limit": 100,
"sample": { "method": "random", "size": 100 }
}For count, send criteria and mapping without search pagination/sampling fields. all is an AND over criteria. Suppression must exclude the specified external IDs, including across pages.
| Field | Operator | Required operand |
|---|---|---|
segment | in | Nonempty string array values. |
externalId | in | Nonempty string array values. |
signupAt | between | ISO datetime strings from and to. |
isActive | eq | Boolean value. |
attr.<name> | eq or in | Corresponding value or values; the attribute must be declared filterable. |
Place search sample at the top level. The published 0.1.2 schemas also accept criteria.sample, and its search handler falls back to that when top-level sample is absent. Prefer the top-level form for interoperability.
limit must be a positive integer no greater than ROW_CAP, currently 1,000. Return at most the requested limit. Treat nextCursor as opaque and echo it verbatim with the same query; it must resume deterministically. Do not silently report a truncated page length as an exact count.
Canonical rows
{
"rows": [{
"externalId": "user_47",
"email": "participant@example.com",
"segment": "team",
"isActive": true
}],
"nextCursor": "OPAQUE_CURSOR"
}externalId is a nonempty stable string; email must be a valid email. Optional fields are name, segment, ISO datetime signupAt, boolean isActive/contactable, and an attributes object. Include optional fields only when mapped and permitted to return. A filterable attribute is filter-only unless it is also declared returnable.
Validate with the contract package
$ npm install @askdepth/audience-contractimport {
SearchRequestSchema,
SearchResponseSchema,
ROW_CAP,
CONTRACT_VERSION,
} from '@askdepth/audience-contract';
const request = SearchRequestSchema.parse({
criteria: { all: [] },
mapping: { id: 'externalId', email_address: 'email' },
limit: Math.min(100, ROW_CAP),
});
const response = SearchResponseSchema.safeParse({ rows: [] });
console.log(CONTRACT_VERSION, request.limit, response.success);Schemas use Zod 4 and strip unrecognized object keys. They do not replace conformance checks: for example, a schema can strip an unexpected field without proving that your endpoint never leaked it over the wire.
The package also exports HealthResponseSchema, SchemaResponseSchema, CountRequestSchema, CountResponseSchema, QuerySchema, CanonicalFieldSchema, and CandidateRowSchema. DEFAULT_BASE_PATH and ENDPOINTS contain the route constants. Use these public exports rather than maintaining a second copy of route strings or row limits.
computeSupportedUntil(nextMajorReleasedAt) computes a support-end ISO timestamp by adding 365 calendar days to the next major's release date. It is a date helper, not a promise that a particular deployed connector has passed version negotiation; use the advertised CONTRACT_VERSION for compatibility checks.
Error envelopes
{ "error": { "code": "invalid_signature", "message": "Signature is invalid." } }The contract exports ErrorResponseSchema and CONNECTOR_ERROR_CODES: unauthorized, invalid_signature, expired_timestamp, malformed_request, unsupported_capability, limit_exceeded, invalid_cursor, not_found, method_not_allowed, adapter_error, timeout, and internal.
Do not include row contents, queries, secrets, or connection strings in errors. The shipped Express shim can additionally produce shim_misconfigured before a request reaches the contract handler when raw-body setup fails; fix the middleware rather than treating that as a normal query error.
