Iniciar sesión Crear cuenta
Referencia de la API

IA

El asistente de IA: escribir correos electrónicos, mejorar textos, sugerir asuntos, explicar problemas de entregabilidad y armar automatizaciones.

El asistente de IA escribe y mejora correos electrónicos y explica cómo resolver problemas de entregabilidad. Cada respuesta usa 1 crédito del plan (Gratis: 10, Profesional: 100, Escala: 1.000 al mes). Los créditos se renuevan el día 1 de cada mes (UTC).

Además de escribir correos electrónicos, la IA arma automatizaciones a partir de una petición en lenguaje natural, traduce mensajes de los buzones de entrada y, activada en un buzón, lee cada correo electrónico nuevo y prepara borradores de respuesta. Todos los endpoints exigen una clave de acceso total.

#Cómo funciona

Generar una respuesta puede tardar desde unos segundos hasta más de un minuto, por eso las solicitudes son asíncronas:

  1. Haces el POST (por ejemplo, POST /ai/subjects). Postal valida los campos, comprueba que haya créditos y responde al instante con 202 Accepted y una tarea: {"object": "ai_task", "id": "…", "status": "pending"}.
  2. Consultas GET /ai/tasks/{id} cada 1 o 2 segundos. Mientras la IA trabaja, el status es pending o processing.
  3. Cuando está lista, la misma consulta devuelve la respuesta completa, con status: "completed", el task_id y el saldo de credits. Si la tarea falla, la consulta devuelve el error, con el status HTTP correspondiente.
Solicitud y consulta

cURL

# 1. Pede a tarefa (responde 202 com o ID)
curl -X POST https://api.postal.so/ai/subjects \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Seu pedido #10482 saiu para entrega e chega em até 3 dias úteis."}'

# 2. Consulta até "status" ser "completed" (ou vir um erro)
curl https://api.postal.so/ai/tasks/019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const headers = {
  Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  'Content-Type': 'application/json',
};

// 1. Pede a tarefa: a resposta chega na hora, com status 202.
const request = await fetch('https://api.postal.so/ai/subjects', {
  method: 'POST',
  headers,
  body: JSON.stringify({ content: 'Seu pedido #10482 saiu para entrega e chega em até 3 dias úteis.' }),
});
const task = await request.json(); // { object: 'ai_task', id, status: 'pending' }

// 2. Consulta a cada 2 segundos até a resposta ficar pronta (ou dar erro).
async function waitFor(id) {
  for (let attempt = 0; attempt < 90; attempt++) {
    const response = await fetch(`https://api.postal.so/ai/tasks/${id}`, { headers });
    const body = await response.json();

    if (!response.ok) {
      throw new Error(`${body.name}: ${body.message}`); // a tarefa falhou
    }

    if (body.status === 'completed') {
      return body;
    }

    await new Promise((resolve) => setTimeout(resolve, 2000));
  }

  throw new Error('A IA demorou demais para responder.');
}

const result = await waitFor(task.id);
console.log(result.subjects);

Python

import os
import time
import requests

HEADERS = {"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"}

# 1. Pede a tarefa: a resposta chega na hora, com status 202.
task = requests.post(
    "https://api.postal.so/ai/subjects",
    headers=HEADERS,
    json={"content": "Seu pedido #10482 saiu para entrega e chega em até 3 dias úteis."},
    timeout=10,
).json()

# 2. Consulta a cada 2 segundos até a resposta ficar pronta (ou dar erro).
def wait_for(task_id, limit=90):
    for _ in range(limit):
        response = requests.get(f"https://api.postal.so/ai/tasks/{task_id}", headers=HEADERS, timeout=10)
        body = response.json()

        if not response.ok:
            raise RuntimeError(f"{body['name']}: {body['message']}")  # a tarefa falhou

        if body["status"] == "completed":
            return body

        time.sleep(2)

    raise TimeoutError("A IA demorou demais para responder.")

result = wait_for(task["id"])
print(result["subjects"])

PHP

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://api.postal.so',
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'http_errors' => false,
]);

// 1. Pede a tarefa: a resposta chega na hora, com status 202.
$task = json_decode($client->post('ai/subjects', [
    'json' => ['content' => 'Seu pedido #10482 saiu para entrega e chega em até 3 dias úteis.'],
])->getBody(), true);

// 2. Consulta a cada 2 segundos até a resposta ficar pronta (ou dar erro).
for ($attempt = 0; $attempt < 90; $attempt++) {
    $response = $client->get("ai/tasks/{$task['id']}");
    $body = json_decode($response->getBody(), true);

    if ($response->getStatusCode() >= 400) {
        throw new RuntimeException("{$body['name']}: {$body['message']}"); // a tarefa falhou
    }

    if ($body['status'] === 'completed') {
        print_r($body['subjects']);
        break;
    }

    sleep(2);
}

Las tareas están disponibles durante 24 horas; pasado ese tiempo, la consulta responde 404.

#Créditos

El crédito se cuenta cuando la IA devuelve una respuesta válida. No consumen créditos las solicitudes rechazadas al instante (campos no válidos, sin créditos) ni las tareas que fallan antes de que la IA responda: servicio ocupado (ai_unavailable), solicitud rechazada por la IA (ai_refused), respuesta demasiado larga o fuera del formato (ai_error). El saldo también aparece en GET /usage (ai_credits).

UsoCuánto cuesta
Escribir, mejorar, asuntos y diagnóstico1 crédito por respuesta
Asistente de automatizaciones1 crédito por mensaje, aunque la IA necesite una segunda pasada para corregir su propio borrador
Traducir un mensaje y mejorar las instrucciones del agente del buzón de entrada1 crédito por respuesta
Agente del buzón de entrada1 crédito por cada correo electrónico nuevo que lee

En cada respuesta lista, credits trae el saldo del mes:

CampoQué es
usedCréditos usados este mes (UTC).
limitCréditos del plan por mes. null: sin límite. 0: el plan no incluye IA, y toda solicitud responde 429 ai_credits_exhausted.
remainingLo que queda (null si no hay límite).
resets_atCuándo se renuevan los créditos: el día 1 del mes siguiente, a la medianoche UTC.

Sin pedir una respuesta, lee el mismo saldo en GET /usage: ai_credits tiene los mismos campos, y features.ai_assistant indica si el servidor y el plan permiten usar la IA. Los créditos de cada plan están en GET /plans (limits.ai_credits).

#La IA en los buzones de entrada

Estas funciones de IA están en la página de Recibidos y buzones de entrada. Usan el mismo flujo asíncrono (202 y GET /ai/tasks/{id}) y los mismos créditos:

#Escribir un correo electrónico

POST/ai/write-email

Clave de acceso total

Escribe el asunto, el preencabezado y el cuerpo a partir de una descripción. En el modo blocks, el cuerpo viene en bloques del editor visual (heading, text, button, divider, spacer, footer); en el modo html, viene un documento HTML completo y la versión en texto. La respuesta final (al lado) llega por GET /ai/tasks/{id}.

En el modo blocks (el predeterminado), la respuesta trae subject, preheader y blocks, una lista de hasta 40 bloques sin id (quien los usa se los asigna): heading (html, level de 1 a 3, align), text y footer (html y align), button (label, url, align), divider y spacer (height en píxeles, de 4 a 160). El texto de los bloques solo tiene <b>, <i>, <u>, <a> y <br>, y las variables aparecen como {{{NAME}}}. En el modo html, los scripts, los formularios y los manejadores de eventos se eliminan del documento.

Cuerpo de la solicitud

  • briefstringobligatorio
    Lo que debe decir el correo electrónico, de 10 a 2.000 caracteres.
  • tonestring
    professional (predeterminado), friendly, formal, casual o persuasive.
  • languagestring
    pt-BR (predeterminado), en o es.
  • modestring
    blocks (predeterminado) o html.

Errores

  • 422validation_error
    Campo ausente o fuera de los límites.
  • 429ai_credits_exhausted
    Se acabaron los créditos de IA del mes. Se renuevan el día 1 (UTC).
  • 503ai_not_configured
    El asistente de IA no está disponible.
POST /ai/write-email

cURL

curl -X POST https://api.postal.so/ai/write-email \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brief": "Avisar que o pedido saiu para entrega, com o link de rastreio e o prazo de 3 dias úteis.",
    "tone": "friendly",
    "mode": "html"
  }'

Node.js

const response = await fetch('https://api.postal.so/ai/write-email', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    brief: 'Avisar que o pedido saiu para entrega, com o link de rastreio e o prazo de 3 dias úteis.',
    tone: 'friendly',
    mode: 'html',
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/ai/write-email",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "brief": "Avisar que o pedido saiu para entrega, com o link de rastreio e o prazo de 3 dias úteis.",
        "tone": "friendly",
        "mode": "html",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('ai/write-email', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'brief' => 'Avisar que o pedido saiu para entrega, com o link de rastreio e o prazo de 3 dias úteis.',
        'tone' => 'friendly',
        'mode' => 'html',
    ],
]);

$data = json_decode($response->getBody(), true);
202Respuesta (tarea creada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Respuesta final en GET /ai/tasks/{id}
{
  "object": "ai_email",
  "subject": "Seu pedido está a caminho",
  "preheader": "Acompanhe a entrega pelo link de rastreio.",
  "html": "<!DOCTYPE html><html lang=\"pt-BR\">…</html>",
  "text": "Olá, {{{NAME}}}! Seu pedido saiu para entrega…",
  "task_id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "completed",
  "credits": {
    "used": 13,
    "limit": 100,
    "remaining": 87,
    "resets_at": "2026-11-01T00:00:00Z"
  }
}
200Respuesta final en el modo blocks
{
  "object": "ai_email",
  "subject": "Seu pedido está a caminho",
  "preheader": "Acompanhe a entrega pelo link de rastreio.",
  "blocks": [
    {
      "type": "heading",
      "html": "Seu pedido está a caminho",
      "level": 1,
      "align": "left"
    },
    {
      "type": "text",
      "html": "Olá, {{{NAME}}}! O pedido #10482 saiu para entrega e chega em até 3 dias úteis.",
      "align": "left"
    },
    {
      "type": "button",
      "label": "Rastrear pedido",
      "url": "{{{TRACKING_URL}}}",
      "align": "left"
    },
    {
      "type": "footer",
      "html": "Você recebe este e-mail porque fez um pedido na Loja Exemplo.",
      "align": "center"
    }
  ],
  "task_id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "completed",
  "credits": {
    "used": 13,
    "limit": 100,
    "remaining": 87,
    "resets_at": "2026-11-01T00:00:00Z"
  }
}

#Mejorar un texto

POST/ai/improve-text

Clave de acceso total

Reescribe un fragmento manteniendo los enlaces y las variables. Acepta formato simple (<b>, <i>, <u>, <a>, <br>).

Cuerpo de la solicitud

  • htmlstringobligatorio
    El texto, hasta 5.000 caracteres.
  • improvementstringobligatorio
    shorter (más corto), formal, friendly o grammar (solo corrige gramática y ortografía).

Errores

  • 422validation_error
    Campo ausente o fuera de los límites.
  • 429ai_credits_exhausted
    Se acabaron los créditos de IA del mes. Se renuevan el día 1 (UTC).
  • 503ai_not_configured
    El asistente de IA no está disponible.
POST /ai/improve-text

cURL

curl -X POST https://api.postal.so/ai/improve-text \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "Oi, seu pedido foi enviado e logo logo vai chegar ai na sua casa, qualquer coisa fala com a gente.",
    "improvement": "grammar"
  }'

Node.js

const response = await fetch('https://api.postal.so/ai/improve-text', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html: 'Oi, seu pedido foi enviado e logo logo vai chegar ai na sua casa, qualquer coisa fala com a gente.',
    improvement: 'grammar',
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/ai/improve-text",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "html": "Oi, seu pedido foi enviado e logo logo vai chegar ai na sua casa, qualquer coisa fala com a gente.",
        "improvement": "grammar",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('ai/improve-text', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'html' => 'Oi, seu pedido foi enviado e logo logo vai chegar ai na sua casa, qualquer coisa fala com a gente.',
        'improvement' => 'grammar',
    ],
]);

$data = json_decode($response->getBody(), true);
202Respuesta (tarea creada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Respuesta final en GET /ai/tasks/{id}
{
  "object": "ai_text",
  "html": "Oi! Seu pedido foi enviado e logo vai chegar aí na sua casa. Qualquer dúvida, fale com a gente.",
  "task_id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "completed",
  "credits": {
    "used": 13,
    "limit": 100,
    "remaining": 87,
    "resets_at": "2026-11-01T00:00:00Z"
  }
}

#Sugerir asuntos

POST/ai/subjects

Clave de acceso total

Sugiere hasta 5 asuntos distintos para el contenido.

Cuerpo de la solicitud

  • contentstringobligatorio
    El contenido del correo electrónico, de 10 a 20.000 caracteres.
  • subjectstring
    El asunto actual, hasta 200 caracteres, para que la IA lo tenga en cuenta.

Errores

  • 422validation_error
    Campo ausente o fuera de los límites.
  • 429ai_credits_exhausted
    Se acabaron los créditos de IA del mes. Se renuevan el día 1 (UTC).
  • 503ai_not_configured
    El asistente de IA no está disponible.
POST /ai/subjects

cURL

curl -X POST https://api.postal.so/ai/subjects \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Seu pedido #10482 saiu para entrega e chega em até 3 dias úteis. Acompanhe pelo link de rastreio."
  }'

Node.js

const response = await fetch('https://api.postal.so/ai/subjects', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    content: 'Seu pedido #10482 saiu para entrega e chega em até 3 dias úteis. Acompanhe pelo link de rastreio.',
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/ai/subjects",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "content": "Seu pedido #10482 saiu para entrega e chega em até 3 dias úteis. Acompanhe pelo link de rastreio.",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('ai/subjects', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'content' => 'Seu pedido #10482 saiu para entrega e chega em até 3 dias úteis. Acompanhe pelo link de rastreio.',
    ],
]);

$data = json_decode($response->getBody(), true);
202Respuesta (tarea creada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Respuesta final en GET /ai/tasks/{id}
{
  "object": "ai_subjects",
  "subjects": [
    "Seu pedido #10482 saiu para entrega",
    "Chega em até 3 dias úteis",
    "Acompanhe seu pedido em tempo real",
    "Boa notícia: seu pedido está a caminho",
    "Quer saber onde está seu pedido?"
  ],
  "task_id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "completed",
  "credits": {
    "used": 13,
    "limit": 100,
    "remaining": 87,
    "resets_at": "2026-11-01T00:00:00Z"
  }
}

#Explicar el diagnóstico

POST/ai/emails/{id}/fix-insights

Clave de acceso total

Explica cómo corregir las verificaciones que fallaron en el diagnóstico de entregabilidad de un correo electrónico enviado. El texto sale en el idioma del parámetro language (pt-BR, en o es); sin él, en el idioma de la solicitud: Accept-Language: pt-BR o en, y en inglés cuando la clave de API no envía el encabezado. Si el correo electrónico no tiene problemas, la tarea termina con 422 validation_error, sin usar créditos.

Parámetros de ruta

  • idstringobligatorio
    ID del correo.

Cuerpo de la solicitud (opcional)

  • languagestring
    Idioma del texto: pt-BR, en o es. Predeterminado: el idioma de la solicitud (Accept-Language), en inglés sin el encabezado.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
  • 422validation_error
    language distinto de pt-BR, en y es, o el correo electrónico no tiene verificaciones con problemas.
  • 429ai_credits_exhausted
    Se acabaron los créditos de IA del mes. Se renuevan el día 1 (UTC).
  • 503ai_not_configured
    El asistente de IA no está disponible.
POST /ai/emails/{id}/fix-insights

cURL

curl -X POST https://api.postal.so/ai/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/fix-insights \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "language": "pt-BR"
  }'

Node.js

const response = await fetch('https://api.postal.so/ai/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/fix-insights', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    language: 'pt-BR',
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/ai/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/fix-insights",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "language": "pt-BR",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('ai/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/fix-insights', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'language' => 'pt-BR',
    ],
]);

$data = json_decode($response->getBody(), true);
202Respuesta (tarea creada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Respuesta final en GET /ai/tasks/{id}
{
  "object": "ai_insights",
  "summary": "Um link sem HTTPS pode fazer o e-mail parecer inseguro para os filtros de spam.",
  "fixes": [
    {
      "code": "https_links",
      "title": "Use HTTPS em todos os links",
      "steps": [
        "Troque http:// por https:// no link do rastreio.",
        "Confirme que o site abre com certificado válido."
      ]
    }
  ],
  "task_id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "completed",
  "credits": {
    "used": 13,
    "limit": 100,
    "remaining": 87,
    "resets_at": "2026-11-01T00:00:00Z"
  }
}

#Armar una automatización con la IA

POST/ai/automations/{id}/assist

Clave de acceso total

El asistente del constructor de automatizaciones: envías la conversación hasta ahora (terminando con la petición de la persona) y el borrador actual, y responde con una frase y los cambios en el borrador (disparador, pasos, nombre, reentrada), ya comprobados con las mismas reglas de POST /automations. No se guarda, activa ni envía nada: para que valga, aplica el definition devuelto en tu editor y guarda con PATCH /automations/{id}. La automatización de la ruta debe existir (puede ser un borrador); da el contexto, y el definition del cuerpo es el que vale. La respuesta final llega por GET /ai/tasks/{id}.

El asistente conoce lo que tiene el equipo (propiedades, segmentos, temas, plantillas, formularios, etiquetas, campañas, eventos recientes y dominios de envío). Si el borrador que arma rompe alguna regla, hace una segunda pasada para corregirlo; los cambios que siguen siendo no válidos se dejan fuera, en skipped. Las dos pasadas cuestan 1 crédito.

Las frases (reply y los summary) salen en portugués con Accept-Language: pt-BR; sin el encabezado, en inglés.

Parámetros de ruta

  • idstringobligatorio
    ID de la automatización.

Cuerpo de la solicitud

  • messagesobject[]obligatorio
    La conversación, de la más antigua a la más reciente, hasta 50 mensajes (solo los 12 últimos llegan a la IA). El último debe ser del usuario.
    • rolestringobligatorio
      user o assistant.
    • contentstringobligatorio
      El texto, hasta 4.000 caracteres (2.000 en el último mensaje, que es la petición).
  • definitionobjectobligatorio
    El borrador actual, en el formato de la automatización. Hasta 64 KB.
    • namestring | null
      Nombre, hasta 255 caracteres.
    • triggerobjectobligatorio
      El disparador, con type.
    • reentrystring
      once o every_time.
    • stepsobject[]obligatorio
      Los pasos. Puede ser una lista vacía, pero debe enviarse.
  • notesstring[]
    Hasta 10 avisos de hasta 500 caracteres sobre lo que la persona hizo en el editor desde la última respuesta, como “creé la propiedad plan” o “deshice los cambios”.
Campo de la respuestaQué es
definitionEl borrador ya con los cambios, normalizado (los pasos reciben id y todos los campos de config). Si nada cambió, es lo que enviaste.
changedtrue si el definition es distinto de lo que enviaste.
operationsLos cambios aplicados, en orden: op (set_trigger, add_step, update_step, remove_step, move_step, set_entry_filters, set_reentry o rename_automation), un summary para mostrar a la persona y el step_id del paso afectado (o null).
pending_actionsRecursos que el flujo necesita y que aún no existen: create_contact_property, create_segment o create_topic, cada uno con id, summary y payload. La API no crea nada: después de que la persona confirme, crea el recurso en propiedades, segmentos o temas y cambia el ID provisional new:<ref> del definition por el ID creado.
skippedCambios que la IA propuso y que no pudieron aplicarse, con el motivo en message.
issuesLo que aún falta o está mal en el definition (cada uno con step_id y message, como en GET /automations/{id}). Los pendientes solo impiden la activación.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
  • 422validation_error
    messages vacío, el último mensaje no es del usuario o supera los 2.000 caracteres, definition sin trigger.type o steps, o mayor de 64 KB.
  • 429ai_credits_exhausted
    Se acabaron los créditos de IA del mes. Se renuevan el día 1 (UTC).
  • 503ai_not_configured
    El asistente de IA no está disponible.
POST /ai/automations/{id}/assist

cURL

curl -X POST https://api.postal.so/ai/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/assist \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Accept-Language: pt-BR" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": "Quando alguém começar o teste grátis, mande as boas-vindas. Se abrir em até 2 dias, ponha num segmento Engajados; se não, mande um lembrete."
      }
    ],
    "definition": {
      "name": "Boas-vindas do teste grátis",
      "trigger": {
        "type": "event",
        "event": "trial.started"
      },
      "reentry": "once",
      "steps": []
    }
  }'

Node.js

const response = await fetch('https://api.postal.so/ai/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/assist', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
    'Accept-Language': 'pt-BR',
  },
  body: JSON.stringify({
    messages: [
      {
        role: 'user',
        content: 'Quando alguém começar o teste grátis, mande as boas-vindas. Se abrir em até 2 dias, ponha num segmento Engajados; se não, mande um lembrete.',
      },
    ],
    definition: {
      name: 'Boas-vindas do teste grátis',
      trigger: {
        type: 'event',
        event: 'trial.started',
      },
      reentry: 'once',
      steps: [],
    },
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/ai/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/assist",
    headers={
        "Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}",
        "Accept-Language": "pt-BR",
    },
    json={
        "messages": [
            {
                "role": "user",
                "content": "Quando alguém começar o teste grátis, mande as boas-vindas. Se abrir em até 2 dias, ponha num segmento Engajados; se não, mande um lembrete.",
            },
        ],
        "definition": {
            "name": "Boas-vindas do teste grátis",
            "trigger": {
                "type": "event",
                "event": "trial.started",
            },
            "reentry": "once",
            "steps": [],
        },
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('ai/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/assist', [
    'headers' => [
        'Authorization' => 'Bearer '.getenv('POSTAL_API_KEY'),
        'Accept-Language' => 'pt-BR',
    ],
    'json' => [
        'messages' => [
            [
                'role' => 'user',
                'content' => 'Quando alguém começar o teste grátis, mande as boas-vindas. Se abrir em até 2 dias, ponha num segmento Engajados; se não, mande um lembrete.',
            ],
        ],
        'definition' => [
            'name' => 'Boas-vindas do teste grátis',
            'trigger' => [
                'type' => 'event',
                'event' => 'trial.started',
            ],
            'reentry' => 'once',
            'steps' => [],
        ],
    ],
]);

$data = json_decode($response->getBody(), true);
202Respuesta (tarea creada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Respuesta final en GET /ai/tasks/{id}
{
  "object": "ai_automation_assist",
  "reply": "Montei o fluxo: manda as boas-vindas, espera até 2 dias pela abertura e, se abriu, coloca o contato no segmento Engajados; se não abriu, manda um lembrete. Falta escolher o template do lembrete, e o segmento Engajados ainda precisa ser criado.",
  "definition": {
    "name": "Boas-vindas do teste grátis",
    "trigger": {
      "type": "event",
      "event": "trial.started",
      "filters": []
    },
    "reentry": "once",
    "steps": [
      {
        "id": "welcome",
        "type": "send_email",
        "config": {
          "template_id": "0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
          "from": null,
          "subject": null,
          "reply_to": null,
          "detect_replies": false,
          "variables": {}
        }
      },
      {
        "id": "wait_open",
        "type": "wait_for",
        "config": {
          "step_id": "welcome",
          "activity": "opened",
          "link_contains": null,
          "timeout": {
            "amount": 2,
            "unit": "days"
          }
        },
        "branches": {
          "yes": [
            {
              "id": "tag_engaged",
              "type": "update_contact",
              "config": {
                "actions": [
                  {
                    "type": "add_to_segment",
                    "segment_id": "new:engaged"
                  }
                ]
              }
            }
          ],
          "no": [
            {
              "id": "reminder",
              "type": "send_email",
              "config": {
                "template_id": null,
                "from": null,
                "subject": null,
                "reply_to": null,
                "detect_replies": false,
                "variables": {}
              }
            }
          ]
        }
      }
    ]
  },
  "changed": true,
  "operations": [
    {
      "op": "set_trigger",
      "summary": "Gatilho definido: Evento recebido (trial.started).",
      "step_id": null
    },
    {
      "op": "add_step",
      "summary": "Passo adicionado: Enviar e-mail (Boas-vindas do teste).",
      "step_id": "welcome"
    },
    {
      "op": "add_step",
      "summary": "Passo adicionado: Esperar até (abriu, no máximo 2 dias).",
      "step_id": "wait_open"
    },
    {
      "op": "add_step",
      "summary": "Passo adicionado: Atualizar contato.",
      "step_id": "tag_engaged"
    },
    {
      "op": "add_step",
      "summary": "Passo adicionado: Enviar e-mail.",
      "step_id": "reminder"
    }
  ],
  "pending_actions": [
    {
      "id": "action_1",
      "type": "create_segment",
      "summary": "Criar o segmento “Engajados”.",
      "payload": {
        "ref": "new:engaged",
        "name": "Engajados",
        "description": "Abriram o e-mail de boas-vindas"
      }
    }
  ],
  "skipped": [],
  "issues": [
    {
      "step_id": "reminder",
      "message": "Escolha o template a enviar."
    }
  ],
  "task_id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "completed",
  "credits": {
    "used": 13,
    "limit": 100,
    "remaining": 87,
    "resets_at": "2026-11-01T00:00:00Z"
  }
}

#Consultar una tarea

GET/ai/tasks/{id}

Clave de acceso total

Mientras la IA trabaja, devuelve solo el status (pending o processing). Cuando termina, devuelve la respuesta de la solicitud (los mismos campos mostrados en cada endpoint anterior) con task_id, status: "completed" y credits. Si la tarea falló, la respuesta es el error, en el formato estándar y con el status HTTP del error. Consulta cada 1 o 2 segundos; la mayoría de las respuestas están listas en hasta un minuto.

Parámetros de ruta

  • idstringobligatorio
    El id devuelto por el POST.

Errores

  • 404not_found
    La tarea no existe, es de otro equipo o pasaron más de 24 horas.
  • 422ai_refused
    La IA rechazó la solicitud. Reescríbela e inténtalo de nuevo.
  • 422validation_error
    No se pudo atender la solicitud (por ejemplo, el correo electrónico no tiene problemas que corregir).
  • 429ai_credits_exhausted
    Los créditos se acabaron antes de que la tarea se ejecutara.
  • 502ai_error
    La IA no devolvió una respuesta válida o tardó demasiado. Inténtalo de nuevo.
  • 503ai_unavailable
    El servicio de IA está ocupado. Inténtalo de nuevo en unos instantes.
GET /ai/tasks/{id}

cURL

curl https://api.postal.so/ai/tasks/019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/ai/tasks/019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b', {
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  },
});

const data = await response.json();

Python

import os
import requests

response = requests.get(
    "https://api.postal.so/ai/tasks/019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->get('ai/tasks/019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Respuesta (aún procesando)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "processing"
}
200Respuesta (lista)
{
  "object": "ai_subjects",
  "subjects": [
    "Seu pedido #10482 saiu para entrega",
    "Chega em até 3 dias úteis"
  ],
  "task_id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "completed",
  "credits": {
    "used": 13,
    "limit": 100,
    "remaining": 87,
    "resets_at": "2026-11-01T00:00:00Z"
  }
}
503Respuesta (la tarea falló)
{
  "statusCode": 503,
  "name": "ai_unavailable",
  "message": "The AI assistant is busy right now. Try again in a moment."
}