> ## Documentation Index
> Fetch the complete documentation index at: https://docs.telzino.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update SMS Template

> Partially updates an SMS template. message_type, organization_id, version, and the server-managed media object (media_key) are immutable and are rejected.

## Path Parameters

<ParamField path="templateId" type="string" required>
  The template's UUID. A value that is not a well-formed UUID returns `400`
  (`{"error": "invalid templateId"}`) — the format check runs before the lookup. A
  well-formed UUID that does not belong to one of the account's organizations returns `404`.
</ParamField>

## Body Parameters

Only author-editable fields are accepted; unknown fields are rejected (`400`).

<ParamField body="name" type="string">Max 100 chars, unique within the organization.</ParamField>
<ParamField body="description" type="string">Max 300 chars.</ParamField>
<ParamField body="body" type="string">Message body with `{{token}}` placeholders. Max 5000 characters; longer is rejected with `400`.</ParamField>
<ParamField body="tags" type="string[]">Free-form tags. Max 50 tags, each up to 64 characters.</ParamField>
<ParamField body="from_number" type="string">Sender identity (phone number, short code, or alphanumeric sender ID) overriding the From Number on the agent's **Send SMS** function. A phone number must be an SMS-capable number on the organization's Twilio account (see [Twilio](/integrations/twilio)) — verified at agent assignment, not here; an unowned number is rejected at send time with error `21606`. Max 64 characters.</ParamField>

<ParamField body="status" type="string">
  `draft`, `active`, or `archived`. Activating a template flagged `is_first_contact` requires opt-out language in the body (e.g. "Reply STOP to opt out"), else `400`.

  **Moving a template off `active` breaks every agent already bound to it.** Archiving — or
  returning it to `draft` — only hides it from *selection*; existing bindings are left in
  place, and from the next call those agents' Send SMS fails with `template_not_found`
  instead of sending. The API does not warn you, and unlike `DELETE` — which is refused while
  any agent references the template — this is not blocked. Re-point the bound agents first.
</ParamField>

<ParamField body="is_first_contact" type="boolean">Whether this may be the first SMS to a recipient.</ParamField>
<ParamField body="allowed_regions" type="string[]">ISO 3166-1 alpha-2 country codes (e.g. `["US","CA"]`). Case-insensitive — values are upper-cased before storage, so `["us"]` and `["US"]` are equivalent. `UK` and `EL` are accepted as aliases for `GB` and `GR`. A code that is not ISO 3166-1 alpha-2 is rejected with `400`: it would match no recipient and silently block every send from the template. Max 300 entries.</ParamField>
<ParamField body="media_url" type="string">MMS media URL (max 2048 chars); may contain `{{token}}` placeholders resolved at send time. The uploaded media object itself (`media_key`) is server-managed and cannot be set here.</ParamField>
<ParamField body="sample_data" type="object">Token → sample-value map (max 100 keys, values max 1000 chars) used to preview the rendered message.</ParamField>

<ParamField body="expected_version" type="integer">
  Optional optimistic-concurrency guard. Pass the `version` you read from the template; if
  it has changed since, the update is refused with `409` instead of overwriting whatever
  landed in between. Omit it to update unconditionally (last write wins).

  This is a guard, not a field — it is never written. The stored `version` is always
  incremented by the server.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X PATCH "https://api.telzino.com/v1/sms-templates/3f2a0000-0000-0000-0000-000000000000" \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{ "body": "Updated message. Reply STOP to opt out.", "status": "active" }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  { "data": { "id": "3f2a...", "status": "active", "version": 4 } }
  ```

  ```json 400 theme={null}
  { "error": "validation_error", "issues": [ { "path": "", "message": "Unrecognized key(s) in object: 'message_type'" } ] }
  ```

  ```json 403 theme={null}
  { "error": "sms_templates_locked" }
  ```

  ```json 409 theme={null}
  { "error": "version_conflict", "message": "This template was changed since you read it. Re-read it and retry." }
  ```
</ResponseExample>

<Note>
  For an unknown or immutable field the offending key appears in `message`, not in `path` —
  `path` is empty, because the rejection belongs to the object rather than to one of its
  properties. Error handling that matches on `issues[].path` will not catch these. For a
  field that exists but holds a bad value (e.g. `name` over 100 characters), `path` is the
  field name as you would expect.
</Note>

## Concurrency

Read-modify-write against this endpoint is last-write-wins by default: two callers that
both `GET` a template and then `PATCH` it will each succeed, and the first one's changes
are lost with no error.

To avoid that, send the `version` you read as `expected_version`. A mismatch returns
`409 version_conflict` and nothing is written — re-read the template, reapply your change,
and retry.

```bash cURL theme={null}
# Read, then write only if nothing changed in between.
VERSION=$(curl -s "https://api.telzino.com/v1/sms-templates/$ID" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.version')

curl -X PATCH "https://api.telzino.com/v1/sms-templates/$ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{ \"body\": \"Updated message. Reply STOP to opt out.\", \"expected_version\": $VERSION }"
```

A `409` is also returned when the new `name` collides with another template in the same
organization — distinguishable by `error`, which is `version_conflict` for a concurrency
clash and carries the duplicate-name message otherwise.

Omitting `expected_version` narrows but does not eliminate the risk: the write is still
guarded against anything landing between this request's own read and its update. That
window is very small, and it returns the same `409 version_conflict`, so a caller need only
handle one case.

## Availability

Returns `403` when SMS Template Manager is locked for the template's organization —
**except** an archive-only patch, which is never blocked. Archiving stays available even
when the feature is otherwise locked, so a locked organization can always tidy up.

An archive-only patch is one whose only *content* change is `{"status": "archived"}`.
`expected_version` may accompany it and still counts as archive-only, since it is a
concurrency guard rather than a field being written:

```jsonc theme={null}
{ "status": "archived" }                        // archive-only
{ "status": "archived", "expected_version": 3 } // also archive-only
{ "status": "archived", "name": "Renamed" }     // NOT archive-only — gated
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.