Register a custom-attribute definition
Registers a new managed attribute definition — the typed schema a Segment's
SendQL predicate can then reference as attr.<name>. enum_values is
required when type is enum and rejected otherwise. An invalid name or
type returns 422 validation_failed.
Safe to repeat. A name that is already registered does not fail merely for being taken:
| Stored definition | Response |
|---|---|
| absent | 201 — the created definition |
| present and identical to the body | 200 — the existing definition, nothing written |
| present with a different schema | 409 attribute_exists_differs |
The 409 attribute_exists_differs carries attribute_id, content_hash
and differs, so the follow-up edit goes straight to PUT /v1/attributes/{id} without paging the registry.
This endpoint never edits. Convergence means it will agree that a
matching definition exists; changing one stays an explicit,
expected_content_hash guarded, previewable edit, because a type change is
disruptive and raises a standing eval_warning on every Segment
referencing the attribute.
What is compared is the same basis the by-name ensure uses: type,
enum_values (as a set — their stored order is presentational) and,
only when the body supplies a non-empty one, description. A blank or
omitted description is not a claim that the attribute has none, so
annotating a definition in the dashboard never breaks a provisioning loop.
For a derived attribute the comparison is the canonical formula.
PUT /v1/attributes/by-name/{name} expresses the same intent more
directly, and is the better call for a declarative integration: the name
lives in the path, so there is no create-shaped body to send for something
that already exists.
Request body
Content type: application/json
name string required Lowercase identifier (letters, digits, underscore), referenced in SendQL as attr.<name>.
type string enum optional Required for an ordinary attribute; ignored when formula is set (the derived type is inferred).
One of: string, number, bool, datetime, enum
enum_values array<string> optional Required when type is enum; must be empty/omitted otherwise.
description string optional formula string optional Creates a derived attribute (SND-1067) whose value SendOps computes and
maintains from the contact's event/activity stream. One aggregate over one
source: exists, count, first, or last of a SES event name (send,
open, click, …) or activity.<name> — e.g. exists(send),
count(activity.order). The value type is inferred; type/enum_values
are ignored. Derived attributes are managed-only (not promotable) and their
formula is immutable (change = delete + recreate). Create-only.
Responses
Errors follow the RFC 7807 problem format — see the error reference.
id string<uuid> required name string required Lowercase identifier; referenced in SendQL as attr.<name>.
type string enum required One of: string, number, bool, datetime, enum
enum_values array<string> optional Allowed values when type is enum; omitted otherwise.
description string required formula string optional For a derived attribute (SND-1067): the aggregate expression SendOps
computes over the contact's event/activity stream, e.g. exists(send),
count(activity.order), last(open). Present only for derived attributes;
their value is maintained by SendOps (read-only) and type is inferred
(exists→bool, count→number, first/last→datetime).
created_at string<date-time> required updated_at string<date-time> required id string<uuid> required name string required Lowercase identifier; referenced in SendQL as attr.<name>.
type string enum required One of: string, number, bool, datetime, enum
enum_values array<string> optional Allowed values when type is enum; omitted otherwise.
description string required formula string optional For a derived attribute (SND-1067): the aggregate expression SendOps
computes over the contact's event/activity stream, e.g. exists(send),
count(activity.order), last(open). Present only for derived attributes;
their value is maintained by SendOps (read-only) and type is inferred
(exists→bool, count→number, first/last→datetime).
created_at string<date-time> required updated_at string<date-time> required POST /v1/attributes and PUT /v1/attributes/by-name/{name}. Either the attribute
exists with a schema other than the one asserted — code is
attribute_exists_differs, and the body carries attribute_id,
content_hash and differs so the edit can be made without searching
for the definition. Nothing was written.
application/problem+json validation_failed Problem. When the refusal is about a
reference — a segment key, template slug, topic or attribute name the
organization does not have — it additionally carries validation_code,
field, line/column, missing, candidates and next_step, so a
client can correct the call without a second round of guessing. See
ValidationProblem.
application/problem+json code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json