Ensure an attribute definition exists
Idempotent "make sure this attribute exists", keyed by the attribute's
name — the call a declarative integration makes on every deploy. Returns
201 when it registered the definition and 200 when the stored one
already matches. Safe to repeat.
It never edits. A name that exists with a different schema returns
409 attribute_exists_differs and the stored definition is left alone.
That response carries attribute_id, content_hash, and differs (the
list of fields that disagree), so the follow-up edit goes straight to
PUT /v1/attributes/{id} with no need to page the registry to find it.
The restraint is deliberate: changing an attribute's type is a
disruptive edit that raises a standing eval_warning on every Segment
referencing it. An hourly provisioning loop must not be able to trigger
that silently, so a real change stays an explicit, expected_content_hash
guarded, previewable edit.
What is compared: type, enum_values, and — only when the body supplies
it — description. An omitted description is not a claim that the
attribute has none, so annotating a definition in the dashboard never
breaks a caller's ensure loop. enum_values compare as a set; their
stored order is presentational. For a derived attribute the comparison is
the canonical formula.
A git-backed definition is read-only here (409 conflict) — the repo owns
it. An invalid name or type returns 422 validation_failed.
Path parameters
name string required The attribute name — a lowercase identifier, referenced in SendQL as attr.<name>.
Request body
Content type: application/json
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.
Compared as a set — order is not part of the schema.
description string optional Asserted only when supplied. Omitting it is not a claim that the attribute has no description, so a definition annotated in the dashboard will not start failing an ensure that never mentioned it.
formula string optional Asserts a derived attribute (see V1AttributeWrite.formula).
Compared in canonical form. A formula is immutable, so a stored
definition whose formula differs is reported as diverging rather
than converged.
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).
origin string enum optional Where the attribute is authored: managed (via the API/dashboard) or
git (synced from a connected repo and read-only via the API).
One of: managed, git
source_path string optional Repo-relative source file, for git-backed attributes only.
last_synced_at string<date-time> optional Time of the last git sync, for git-backed attributes only.
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).
origin string enum optional Where the attribute is authored: managed (via the API/dashboard) or
git (synced from a connected repo and read-only via the API).
One of: managed, git
source_path string optional Repo-relative source file, for git-backed attributes only.
last_synced_at string<date-time> optional Time of the last git sync, for git-backed attributes only.
created_at string<date-time> required updated_at string<date-time> required 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 — or the name belongs to a git-backed definition,
which is read-only here (code is conflict). Nothing was written in
either case.
application/problem+json code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json