# Ensure an attribute definition exists

`PUT /v1/attributes/by-name/{name}`

- Authentication: required (Bearer token)
- Required scope: `api.attributes.manage`

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`

```json
{
  "type": "string",
  "enum_values": [
    "string"
  ],
  "description": "string",
  "formula": "string"
}
```

## Example request

```bash
curl -X PUT 'https://api.sendops.dev/v1/attributes/by-name/string' \
  -H "Authorization: Bearer $SENDOPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"string","enum_values":["string"],"description":"string","formula":"string"}'
```

## Responses

### 200 — The attribute already exists and matches; nothing was written

Content type: `application/json`

```json
{
  "id": "00000000-0000-0000-0000-000000000000",
  "name": "string",
  "type": "string",
  "enum_values": [
    "string"
  ],
  "description": "string",
  "formula": "string",
  "origin": "managed",
  "source_path": "string",
  "last_synced_at": "2026-05-17T20:00:00Z",
  "created_at": "2026-05-17T20:00:00Z",
  "updated_at": "2026-05-17T20:00:00Z"
}
```

### 201 — The attribute was registered

Content type: `application/json`

```json
{
  "id": "00000000-0000-0000-0000-000000000000",
  "name": "string",
  "type": "string",
  "enum_values": [
    "string"
  ],
  "description": "string",
  "formula": "string",
  "origin": "managed",
  "source_path": "string",
  "last_synced_at": "2026-05-17T20:00:00Z",
  "created_at": "2026-05-17T20:00:00Z",
  "updated_at": "2026-05-17T20:00:00Z"
}
```

### 401 — Missing, malformed, or unknown API key

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 403 — Key lacks the required scope or plan limit violated

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 409 — Returned by `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.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 422 — Query parameter or path value failed validation

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 429 — Per-org rate limit exceeded

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 500 — Unexpected server-side failure. The `code` is `internal_error`. The
`request_id` field can be quoted to SendOps support to investigate.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```
