Docs
Pricing Log in
Documentation

Traits

Define the audience traits your account collects — the schema that trait mappings (coming soon) will write onto profiles.

A trait definition is a named attribute — auto_intender, homeowner, beauty_interest — with a type, an optional closed set of allowed values, and a merge strategy describing how a new value should combine with an existing one. Trait definitions are account-wide, not scoped to a single site.

MethodEndpointAuthDescription
POST/traitsAPI keyCreate a trait definition
GET/traitsAPI keyList your trait definitions
GET/traits/{key}API keyGet a single trait definition
PATCH/traits/{key}API keyUpdate a trait definition (PUT also accepted)
DELETE/traits/{key}API keyArchive a trait definition
POST /traits API key

Create a trait definition

FieldTypeRules
keystringRequired, max 64, ^[a-z][a-z0-9_]{1,63}$, unique per account. Immutable after creation.
labelstringRequired, max 128 — the human-readable name
descriptionstringOptional
data_typestringRequired, one of boolean, enum, number, string_list. Immutable after creation — create a new trait instead of changing it.
allowed_valuesarray of stringsRequired for enum/string_list, rejected for boolean/number. For enum, order is rank — first is lowest — used by the keep_highest merge strategy.
merge_strategystringOptional, defaults to overwrite. One of overwrite, keep_first, keep_highest (requires enum or number), append_unique (requires string_list).
ttl_daysintegerOptional, minimum 1. Omit for a trait that never expires.
REQUEST
curl -X POST https://qnaapi.com/api/v1/traits \
  -H "X-API-Key: qna_..." \
  -H "Content-Type: application/json" \
  -d '{
    "key": "auto_intender",
    "label": "Auto purchase intent",
    "data_type": "enum",
    "allowed_values": ["low", "medium", "high"],
    "merge_strategy": "keep_highest",
    "ttl_days": 90
  }'
201 CREATED
{
  "data": {
    "id": 1,
    "key": "auto_intender",
    "label": "Auto purchase intent",
    "description": null,
    "data_type": "enum",
    "allowed_values": ["low", "medium", "high"],
    "merge_strategy": "keep_highest",
    "ttl_days": 90,
    "archived_at": null,
    "created_at": "2026-09-29T00:00:00.000000Z",
    "updated_at": "2026-09-29T00:00:00.000000Z"
  }
}

A duplicate key on your own account returns 422 Unprocessable Entity with a validation error on the key field. The same key is allowed across two different accounts.

GET /traits API key

List your trait definitions

REQUEST
curl -H "X-API-Key: qna_..." https://qnaapi.com/api/v1/traits
Query paramTypeRules
include_archivedbooleanOptional, defaults to false — archived traits are hidden from a default listing.

Returns { "data": [ ...trait objects... ] }, most recently created first.

GET /traits/{key} API key

Get a trait definition

REQUEST
curl -H "X-API-Key: qna_..." https://qnaapi.com/api/v1/traits/auto_intender
PATCH /traits/{key} API key

Update a trait definition

Every field is optional — send only what you want to change. key and data_type cannot be changed and are silently ignored if sent; create a new trait instead.

FieldTypeRules
labelstringOptional, max 128
descriptionstringOptional, nullable
allowed_valuesarrayOptional — append-only. The submitted array's existing entries must match the current ones in the same order; only adding new entries at the end is allowed.
merge_strategystringOptional — same compatibility rules as create, checked against the trait's existing (unchangeable) data_type.
ttl_daysintegerOptional, nullable, minimum 1
REQUEST
curl -X PATCH https://qnaapi.com/api/v1/traits/auto_intender \
  -H "X-API-Key: qna_..." \
  -H "Content-Type: application/json" \
  -d '{ "allowed_values": ["low", "medium", "high", "very_high"] }'
Removing or reordering an existing allowed_values entry returns 422 Unprocessable Entity — a mapping or a profile's already-stored trait value may reference it.
DELETE /traits/{key} API key

Archive a trait definition

REQUEST
curl -X DELETE -H "X-API-Key: qna_..." https://qnaapi.com/api/v1/traits/auto_intender

Returns 200 OK with the archived trait (not 204) so you can see its new archived_at timestamp. Archiving never deletes the row — it only stops future writes and hides it from a default GET /traits listing.