VitrinaAPI

Macros

Saved replies with automated side-effects an agent applies in one click.

Beta
This may change at any time, with a changelog entry and a notice to recent callers — see versioning.

Download the full API pública projection: openapi.json.

Automate replies and assignment explains this resource in prose, with runnable examples.

MethodPathWhat it does
GET/macrosList macros
POST/macrosCreate a macro
DELETE/macros/{id}Delete a macro
GET/macros/{id}Fetch one macro
PUT/macros/{id}Update a macro
POST/macros/{id}/applyRun a macro as-is
GET/macros/{id}/prepareResolve a macro for the composer
POST/macros/{id}/runSend the reviewed draft and run the side-effects

GET /macros

List macros

Every macro in the workspace with its content, actions, params and usage counters. Unfiltered — active, visibility and available_for come back on each row for the caller to filter on, and inactive macros are included.

curl https://api.vitrinadev.com/api/v1/macros \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": [
    {
      "id": "d3d3d3d3-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "name": "Cierre agradecido",
      "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
      "category": "cierre",
      "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
      "shortcut": "cierre",
      "actions": [
        {
          "id": "ac7a0000-0000-4000-8000-000000000001",
          "type": "add_tag",
          "value": [
            "resuelto"
          ]
        },
        {
          "id": "ac7a0000-0000-4000-8000-000000000002",
          "type": "change_status",
          "value": "resolved"
        }
      ],
      "params": [
        {
          "key": "nombre",
          "label": "Nombre del cliente",
          "input_type": "text"
        }
      ],
      "icon": "play",
      "visibility": "public",
      "available_for": "everyone",
      "run_when": [
        "manual"
      ],
      "active": true,
      "usage_count": 42,
      "last_used_at": "2026-09-15T18:20:00.000Z",
      "created_at": "2026-01-10T13:00:00.000Z",
      "updated_at": "2026-09-10T13:00:00.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /macros

Create a macro

Only name is required. An action-only macro is valid — one that just changes status and adds a label, with no content, is a normal thing to build.

actions is an untyped list: the step objects are stored as given and validated only when the macro RUNS. A typo in a step type creates cleanly and then reports unknown action type on every execution, so run a macro once after authoring it.

Each entry in params declares one [[manual]] placeholder, and its key must match the placeholder text in content for the composer to pair them up. Defaults: icon: "play", visibility: "public", available_for: "everyone", run_when: ["manual"], active: true. Answers 201.

Body

FieldTypeRequiredConstraints
namestringyesmín. 1, máx. 200
descriptionstring | null—máx. 2000
categorystring | null—máx. 100
contentstring—máx. 8000, por defecto ""
shortcutstring | null—máx. 50
actionsany[]—
paramsobject[]—
iconstring—máx. 60
visibilitypublic \private—
available_forstring—máx. 120
run_whenstring[]—
activeboolean—
curl -X POST https://api.vitrinadev.com/api/v1/macros \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Cierre agradecido",
    "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
    "actions": [
      {
        "id": "ac7a0000-0000-4000-8000-000000000001",
        "type": "add_tag",
        "value": [
          "resuelto"
        ]
      }
    ],
    "params": [
      {
        "key": "nombre",
        "label": "Nombre del cliente",
        "input_type": "text"
      }
    ]
  }'

Example response (201)

{
  "data": {
    "id": "d3d3d3d3-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "name": "Cierre agradecido",
    "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
    "category": "cierre",
    "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
    "shortcut": "cierre",
    "actions": [
      {
        "id": "ac7a0000-0000-4000-8000-000000000001",
        "type": "add_tag",
        "value": [
          "resuelto"
        ]
      },
      {
        "id": "ac7a0000-0000-4000-8000-000000000002",
        "type": "change_status",
        "value": "resolved"
      }
    ],
    "params": [
      {
        "key": "nombre",
        "label": "Nombre del cliente",
        "input_type": "text"
      }
    ],
    "icon": "play",
    "visibility": "public",
    "available_for": "everyone",
    "run_when": [
      "manual"
    ],
    "active": true,
    "usage_count": 42,
    "last_used_at": "2026-09-15T18:20:00.000Z",
    "created_at": "2026-01-10T13:00:00.000Z",
    "updated_at": "2026-09-10T13:00:00.000Z"
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /macros/{id}

Delete a macro

Removes the macro. Everything it already did — messages sent, labels applied, status changes — stays; a macro is an action, not a relationship. Set active: false to retire one without losing its usage history.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/macros/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429

GET /macros/{id}

Fetch one macro

The macro as stored, including usage_count and last_used_at.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/macros/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "d3d3d3d3-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "name": "Cierre agradecido",
    "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
    "category": "cierre",
    "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
    "shortcut": "cierre",
    "actions": [
      {
        "id": "ac7a0000-0000-4000-8000-000000000001",
        "type": "add_tag",
        "value": [
          "resuelto"
        ]
      },
      {
        "id": "ac7a0000-0000-4000-8000-000000000002",
        "type": "change_status",
        "value": "resolved"
      }
    ],
    "params": [
      {
        "key": "nombre",
        "label": "Nombre del cliente",
        "input_type": "text"
      }
    ],
    "icon": "play",
    "visibility": "public",
    "available_for": "everyone",
    "run_when": [
      "manual"
    ],
    "active": true,
    "usage_count": 42,
    "last_used_at": "2026-09-15T18:20:00.000Z",
    "created_at": "2026-01-10T13:00:00.000Z",
    "updated_at": "2026-09-10T13:00:00.000Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

PUT /macros/{id}

Update a macro

A PUT that behaves as a PATCH; at least one field is required. actions and params are each replaced wholesale when sent, so a step list must be sent complete.

Editing takes effect immediately for anyone about to run it. The usage counters are not editable here.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
namestring—mín. 1, máx. 200
descriptionstring | null—máx. 2000
categorystring | null—máx. 100
contentstring—máx. 8000, por defecto ""
shortcutstring | null—máx. 50
actionsany[]—
paramsobject[]—
iconstring—máx. 60
visibilitypublic \private—
available_forstring—máx. 120
run_whenstring[]—
activeboolean—
curl -X PUT https://api.vitrinadev.com/api/v1/macros/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "active": false
  }'

Example response (200)

{
  "data": {
    "id": "d3d3d3d3-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "name": "Cierre agradecido",
    "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
    "category": "cierre",
    "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
    "shortcut": "cierre",
    "actions": [
      {
        "id": "ac7a0000-0000-4000-8000-000000000001",
        "type": "add_tag",
        "value": [
          "resuelto"
        ]
      },
      {
        "id": "ac7a0000-0000-4000-8000-000000000002",
        "type": "change_status",
        "value": "resolved"
      }
    ],
    "params": [
      {
        "key": "nombre",
        "label": "Nombre del cliente",
        "input_type": "text"
      }
    ],
    "icon": "play",
    "visibility": "public",
    "available_for": "everyone",
    "run_when": [
      "manual"
    ],
    "active": false,
    "usage_count": 42,
    "last_used_at": "2026-09-15T18:20:00.000Z",
    "created_at": "2026-01-10T13:00:00.000Z",
    "updated_at": "2026-09-10T13:00:00.000Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /macros/{id}/apply

Run a macro as-is

Executes the macro against a conversation with no chance to review the text first — templates are rendered and the reply is sent straight to the customer. For the reviewed flow use GET /macros/\{id\}/prepare then POST /macros/\{id\}/run; this endpoint is for macros with no manual params, and for automation.

With an empty body it does nothing but bump usage_count and return an empty step log — the legacy "the agent clicked it" call. Execution only begins once conversation_id is supplied.

Answers \{ macro, executed \}, where executed is one \{ type, ok, error \} entry per step in order. A 200 does not mean the macro worked — a failed send and a failed label both appear as ok: false inside a successful response, and the steps after them still ran. Surface the failures; they are not retried.

send_attachment sends a file of the library (GET /media-assets): its value is the file id (asset_name is display-only). It runs in step order, through the same checks as POST /conversations/\{id\}/media-assets/\{assetId\}/send; a refusal reports its code as error (e.g. MEDIA_ASSET_NOT_SENDABLE), a value that is not a file id reports Elige un archivo de la Biblioteca, and a send parked by the política de envíos reports status: pending_approval. mute_conversation is accepted by the builder but not implemented and reports not yet supported. An unrecognised type reports unknown action type.

run_webhook is a FLAG rather than a step: its presence anywhere in actions makes the whole run fire one macro.applied webhook after the loop, with the full step log. Listing it twice still fires once, and its position in the list has no effect.

Ticket-bound steps act on ticket_id when given, otherwise on the conversation’s most recent ticket. A macro with label or stage steps run against a conversation that has no ticket records those steps as failed while the reply still goes out.

usage_count is bumped BEFORE the steps run, so it counts attempts rather than successes.

Attribution follows the credential: a signed-in teammate’s reply is stored as a human agent message, while an API key with no user behind it is stored as an AI-agent message. That also decides the web kill-switch — only a human-initiated run bypasses it.

Scopes: macros:read, conversations:write and messages:send, all three. A reply sent by an API key, a personal token or a connected app goes through the política de envíos like any API send: a Bloqueo or an unacknowledged Advertencia fails the send_message step, and a connected app past its fan-out allowance gets the reply parked for a member's approval — the step is then ok with status: "pending_approval" and an approval_id.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
conversation_iduuid—
ticket_iduuid—
curl -X POST https://api.vitrinadev.com/api/v1/macros/<id>/apply \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001"
  }'

Example response (200)

{
  "data": {
    "macro": {
      "id": "d3d3d3d3-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "name": "Cierre agradecido",
      "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
      "category": "cierre",
      "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
      "shortcut": "cierre",
      "actions": [
        {
          "id": "ac7a0000-0000-4000-8000-000000000001",
          "type": "add_tag",
          "value": [
            "resuelto"
          ]
        },
        {
          "id": "ac7a0000-0000-4000-8000-000000000002",
          "type": "change_status",
          "value": "resolved"
        }
      ],
      "params": [
        {
          "key": "nombre",
          "label": "Nombre del cliente",
          "input_type": "text"
        }
      ],
      "icon": "play",
      "visibility": "public",
      "available_for": "everyone",
      "run_when": [
        "manual"
      ],
      "active": true,
      "usage_count": 42,
      "last_used_at": "2026-09-15T18:20:00.000Z",
      "created_at": "2026-01-10T13:00:00.000Z",
      "updated_at": "2026-09-10T13:00:00.000Z"
    },
    "executed": [
      {
        "type": "add_tag",
        "ok": true,
        "error": null
      },
      {
        "type": "change_status",
        "ok": true,
        "error": null
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /macros/{id}/prepare

Resolve a macro for the composer

Renders the macro against a live conversation WITHOUT sending or changing anything. Answers \{ macro, segments, manual_params, effects \}.

segments is the reply broken into resolved text and still-unfilled placeholders, so a composer can render the draft with gaps in place. manual_params is the [[manual]] params the agent must fill, with their input types and options. effects summarises the non-message actions so the agent can see what else pressing send will do.

The reply text is the macro’s FIRST send_message action, falling back to content. A macro with several send_message steps previews only the first here, though running it sends them all.

Read-only and side-effect free — unlike /apply, it does not touch usage_count.

ParameterInTypeRequiredConstraints
idpathuuidyes
conversation_idquerystringyesmín. 1
curl https://api.vitrinadev.com/api/v1/macros/<id>/prepare \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "macro": {
      "id": "d3d3d3d3-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "name": "Cierre agradecido",
      "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
      "category": "cierre",
      "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
      "shortcut": "cierre",
      "actions": [
        {
          "id": "ac7a0000-0000-4000-8000-000000000001",
          "type": "add_tag",
          "value": [
            "resuelto"
          ]
        },
        {
          "id": "ac7a0000-0000-4000-8000-000000000002",
          "type": "change_status",
          "value": "resolved"
        }
      ],
      "params": [
        {
          "key": "nombre",
          "label": "Nombre del cliente",
          "input_type": "text"
        }
      ],
      "icon": "play",
      "visibility": "public",
      "available_for": "everyone",
      "run_when": [
        "manual"
      ],
      "active": true,
      "usage_count": 42,
      "last_used_at": "2026-09-15T18:20:00.000Z",
      "created_at": "2026-01-10T13:00:00.000Z",
      "updated_at": "2026-09-10T13:00:00.000Z"
    },
    "segments": [
      {
        "text": "Gracias por escribirnos, ",
        "resolved": true
      },
      {
        "text": "[[nombre]]",
        "resolved": false
      }
    ],
    "manual_params": [
      {
        "key": "nombre",
        "label": "Nombre del cliente",
        "input_type": "text"
      }
    ],
    "effects": [
      "Etiqueta: resuelto",
      "Cambia el estado a resolved"
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /macros/{id}/run

Send the reviewed draft and run the side-effects

The second half of the composer flow: takes the text the agent actually approved and executes the macro’s side-effects in one call.

draft_text supersedes the macro’s send_message steps. When it is present that text is sent and every send_message step is skipped, so the customer never receives both. Send it already substituted — param_values are applied to the macro’s own templates (notes, labels), not to your draft.

Omit draft_text and the macro’s own send_message steps run normally, rendered with param_values.

Answers \{ macro, executed \}, where executed is one \{ type, ok, error \} entry per step in order. A 200 does not mean the macro worked — a failed send and a failed label both appear as ok: false inside a successful response, and the steps after them still ran. Surface the failures; they are not retried.

send_attachment sends a file of the library (GET /media-assets): its value is the file id (asset_name is display-only). It runs in step order, through the same checks as POST /conversations/\{id\}/media-assets/\{assetId\}/send; a refusal reports its code as error (e.g. MEDIA_ASSET_NOT_SENDABLE), a value that is not a file id reports Elige un archivo de la Biblioteca, and a send parked by the política de envíos reports status: pending_approval. mute_conversation is accepted by the builder but not implemented and reports not yet supported. An unrecognised type reports unknown action type.

run_webhook is a FLAG rather than a step: its presence anywhere in actions makes the whole run fire one macro.applied webhook after the loop, with the full step log. Listing it twice still fires once, and its position in the list has no effect.

Ticket-bound steps act on ticket_id when given, otherwise on the conversation’s most recent ticket. A macro with label or stage steps run against a conversation that has no ticket records those steps as failed while the reply still goes out.

Delivery is attempted BEFORE the message is persisted, so a channel failure shows up as a failed step rather than as a message in the thread that never left.

Scopes: macros:read, conversations:write and messages:send, all three. A reply sent by an API key, a personal token or a connected app goes through the política de envíos like any API send: a Bloqueo or an unacknowledged Advertencia fails the send_message step, and a connected app past its fan-out allowance gets the reply parked for a member's approval — the step is then ok with status: "pending_approval" and an approval_id.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
conversation_iduuidyes
ticket_iduuid—
draft_textstring—máx. 8000
param_valuesobject—
curl -X POST https://api.vitrinadev.com/api/v1/macros/<id>/run \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "draft_text": "Gracias por escribirnos, María. Cualquier otra duda, aquí estamos.",
    "param_values": {
      "nombre": "María"
    }
  }'

Example response (200)

{
  "data": {
    "macro": {
      "id": "d3d3d3d3-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "name": "Cierre agradecido",
      "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
      "category": "cierre",
      "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
      "shortcut": "cierre",
      "actions": [
        {
          "id": "ac7a0000-0000-4000-8000-000000000001",
          "type": "add_tag",
          "value": [
            "resuelto"
          ]
        },
        {
          "id": "ac7a0000-0000-4000-8000-000000000002",
          "type": "change_status",
          "value": "resolved"
        }
      ],
      "params": [
        {
          "key": "nombre",
          "label": "Nombre del cliente",
          "input_type": "text"
        }
      ],
      "icon": "play",
      "visibility": "public",
      "available_for": "everyone",
      "run_when": [
        "manual"
      ],
      "active": true,
      "usage_count": 42,
      "last_used_at": "2026-09-15T18:20:00.000Z",
      "created_at": "2026-01-10T13:00:00.000Z",
      "updated_at": "2026-09-10T13:00:00.000Z"
    },
    "executed": [
      {
        "type": "add_tag",
        "ok": true,
        "error": null
      },
      {
        "type": "change_status",
        "ok": true,
        "error": null
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

On this page