Skip to content
Docs
Loading documentation…
Open platform
Documentation

Endpoints and field mapping

Understand the four connector routes, query criteria, mapping direction, and response schemas.

@askdepth/audience-contractv0.1.2
On this page

Every route requires a valid signature, including health and schema. Paths below are relative to the configured base path, normally /askdepth/v1.

MethodPathResponse
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

json
{
  "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.

FlagMeaning
externalIdInFilter an explicit list of canonical external IDs.
attributeFiltersFilter declared attr.* fields.
dateRangesFilter signupAt with a datetime range.
randomSampleProduce a random sample rather than the first N rows.
declaredSchemaSchema comes from configuration, as in the REST adapter.

Mapping has two directions

Connector configuration uses canonical field → source column:

ts
const fieldMapping = {
  externalId: 'id',
  email: 'email_address',
  segment: 'plan',
};

The request body's mapping uses source column → canonical field:

json
{
  "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

json
{
  "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.

FieldOperatorRequired operand
segmentinNonempty string array values.
externalIdinNonempty string array values.
signupAtbetweenISO datetime strings from and to.
isActiveeqBoolean value.
attr.<name>eq or inCorresponding 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

json
{
  "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-contract
ts
import {
  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

json
{ "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.