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.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /traits | API key | Create a trait definition |
| GET | /traits | API key | List your trait definitions |
| GET | /traits/{key} | API key | Get a single trait definition |
| PATCH | /traits/{key} | API key | Update a trait definition (PUT also accepted) |
| DELETE | /traits/{key} | API key | Archive a trait definition |
/traits
API key
Create a trait definition
| Field | Type | Rules |
|---|---|---|
| key | string | Required, max 64, ^[a-z][a-z0-9_]{1,63}$, unique per account. Immutable after creation. |
| label | string | Required, max 128 — the human-readable name |
| description | string | Optional |
| data_type | string | Required, one of boolean, enum, number, string_list. Immutable after creation — create a new trait instead of changing it. |
| allowed_values | array of strings | Required for enum/string_list, rejected for boolean/number. For enum, order is rank — first is lowest — used by the keep_highest merge strategy. |
| merge_strategy | string | Optional, defaults to overwrite. One of overwrite, keep_first, keep_highest (requires enum or number), append_unique (requires string_list). |
| ttl_days | integer | Optional, minimum 1. Omit for a trait that never expires. |
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
}'{
"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.
/traits
API key
List your trait definitions
curl -H "X-API-Key: qna_..." https://qnaapi.com/api/v1/traits
| Query param | Type | Rules |
|---|---|---|
| include_archived | boolean | Optional, defaults to false — archived traits are hidden from a default listing. |
Returns { "data": [ ...trait objects... ] }, most recently created first.
/traits/{key}
API key
Get a trait definition
curl -H "X-API-Key: qna_..." https://qnaapi.com/api/v1/traits/auto_intender
/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.
| Field | Type | Rules |
|---|---|---|
| label | string | Optional, max 128 |
| description | string | Optional, nullable |
| allowed_values | array | Optional — 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_strategy | string | Optional — same compatibility rules as create, checked against the trait's existing (unchangeable) data_type. |
| ttl_days | integer | Optional, nullable, minimum 1 |
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"] }'allowed_values entry returns 422 Unprocessable Entity — a mapping or a profile's already-stored trait value may reference it.
/traits/{key}
API key
Archive a trait definition
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.