Referência da API

IA

O assistente de IA: escrever e-mails, melhorar textos, sugerir assuntos e explicar problemas de entregabilidade.

O assistente de IA escreve e melhora e-mails e explica como resolver problemas de entregabilidade. Cada resposta usa 1 crédito do plano (Free: 10, Pro: 100, Scale: 1.000 por mês). Os créditos renovam no dia 1º de cada mês (UTC).

#Como funciona

Gerar uma resposta pode levar de alguns segundos a mais de um minuto, então os pedidos são assíncronos:

  1. Você faz o POST (por exemplo, POST /ai/subjects). A Postal valida os campos, confere se há créditos e responde na hora com 202 Accepted e uma tarefa: {"object": "ai_task", "id": "…", "status": "pending"}.
  2. Você consulta GET /ai/tasks/{id} a cada 1 ou 2 segundos. Enquanto a IA trabalha, o status é pending ou processing.
  3. Quando fica pronta, a mesma consulta devolve a resposta completa, com status: "completed", o task_id e o saldo de credits. Se a tarefa falhar, a consulta devolve o erro, com o status HTTP correspondente.
Pedido e 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);
}

As tarefas ficam disponíveis por 24 horas; depois disso, a consulta responde 404.

#Créditos

O crédito é contado quando a IA devolve uma resposta válida. Não usam créditos os pedidos recusados na hora (campos inválidos, sem créditos) nem as tarefas que falham antes de a IA responder: serviço ocupado (ai_unavailable), pedido recusado pela IA (ai_refused), resposta longa demais ou fora do formato (ai_error). O saldo também aparece em GET /usage (ai_credits).

#Escrever um e-mail

POST/ai/write-email

Chave de acesso total

Escreve assunto, pré-cabeçalho e corpo a partir de uma descrição. No modo blocks, o corpo vem em blocos do editor visual (heading, text, button, divider, spacer, footer); no modo html, vem um documento HTML completo e a versão em texto. A resposta final (ao lado) chega por GET /ai/tasks/{id}.

Corpo da requisição

  • briefstringobrigatório
    O que o e-mail deve dizer, de 10 a 2.000 caracteres.
  • tonestring
    professional (padrão), friendly, formal, casual ou persuasive.
  • languagestring
    pt-BR (padrão), en ou es.
  • modestring
    blocks (padrão) ou html.

Erros

  • 422validation_error
    Campo ausente ou fora dos limites.
  • 429ai_credits_exhausted
    Os créditos de IA do mês acabaram. Eles renovam no dia 1º (UTC).
  • 503ai_not_configured
    O assistente de IA não está disponível.
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);
202Resposta (tarefa criada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Resposta final em 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"
  }
}

#Melhorar um texto

POST/ai/improve-text

Chave de acesso total

Reescreve um trecho mantendo links e variáveis. Aceita formatação simples (<b>, <i>, <u>, <a>, <br>).

Corpo da requisição

  • htmlstringobrigatório
    O texto, até 5.000 caracteres.
  • improvementstringobrigatório
    shorter (mais curto), formal, friendly ou grammar (só corrige gramática e ortografia).

Erros

  • 422validation_error
    Campo ausente ou fora dos limites.
  • 429ai_credits_exhausted
    Os créditos de IA do mês acabaram. Eles renovam no dia 1º (UTC).
  • 503ai_not_configured
    O assistente de IA não está disponível.
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);
202Resposta (tarefa criada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Resposta final em 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 assuntos

POST/ai/subjects

Chave de acesso total

Sugere até 5 assuntos diferentes para o conteúdo.

Corpo da requisição

  • contentstringobrigatório
    O conteúdo do e-mail, de 10 a 20.000 caracteres.
  • subjectstring
    O assunto atual, até 200 caracteres, para a IA levar em conta.

Erros

  • 422validation_error
    Campo ausente ou fora dos limites.
  • 429ai_credits_exhausted
    Os créditos de IA do mês acabaram. Eles renovam no dia 1º (UTC).
  • 503ai_not_configured
    O assistente de IA não está disponível.
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);
202Resposta (tarefa criada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Resposta final em 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 o diagnóstico

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

Chave de acesso total

Explica, em português, como corrigir as verificações que falharam no diagnóstico de entregabilidade de um e-mail enviado. Se o e-mail não tiver problemas, a tarefa termina com 422 validation_error, sem usar créditos.

Parâmetros de caminho

  • idstringobrigatório
    ID do e-mail.

Erros

  • 404not_found
    O recurso não existe ou é de outro time.
  • 429ai_credits_exhausted
    Os créditos de IA do mês acabaram. Eles renovam no dia 1º (UTC).
  • 503ai_not_configured
    O assistente de IA não está disponível.
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"

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}`,
  },
});

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']}"},
    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')],
]);

$data = json_decode($response->getBody(), true);
202Resposta (tarefa criada)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "pending"
}
200Resposta final em 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"
  }
}

#Consultar uma tarefa

GET/ai/tasks/{id}

Chave de acesso total

Enquanto a IA trabalha, devolve só o status (pending ou processing). Quando termina, devolve a resposta do pedido (os mesmos campos mostrados em cada endpoint acima) com task_id, status: "completed" e credits. Se a tarefa falhou, a resposta é o erro, no formato padrão e com o status HTTP do erro. Consulte a cada 1 ou 2 segundos; a maioria das respostas fica pronta em até um minuto.

Parâmetros de caminho

  • idstringobrigatório
    O id devolvido pelo POST.

Erros

  • 404not_found
    A tarefa não existe, é de outro time ou passou de 24 horas.
  • 422ai_refused
    A IA recusou o pedido. Reescreva e tente de novo.
  • 422validation_error
    O pedido não pôde ser atendido (por exemplo, o e-mail não tem problemas para corrigir).
  • 429ai_credits_exhausted
    Os créditos acabaram antes de a tarefa rodar.
  • 502ai_error
    A IA não devolveu uma resposta válida ou demorou demais. Tente de novo.
  • 503ai_unavailable
    O serviço de IA está ocupado. Tente de novo em 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);
200Resposta (ainda processando)
{
  "object": "ai_task",
  "id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "processing"
}
200Resposta (pronta)
{
  "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"
  }
}
503Resposta (tarefa falhou)
{
  "statusCode": 503,
  "name": "ai_unavailable",
  "message": "The AI assistant is busy right now. Try again in a moment."
}