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

# SDK Python

> SDK Python oficial da Arara (ararahq-sdk): cliente síncrono e assíncrono com httpx, modelos Pydantic v2 e retry com tenacity.

## Instalar

```bash theme={null}
pip install ararahq-sdk
```

[![PyPI](https://img.shields.io/pypi/v/ararahq-sdk)](https://pypi.org/project/ararahq-sdk/)

Versão publicada: **2.0.0**. Python 3.8+. Dependências: `httpx`, `pydantic` v2, `pydantic-settings` e `tenacity`. O pacote instala como `ararahq-sdk` e importa como `arara_api_sdk`.

## Configurar

```python theme={null}
import os
from arara_api_sdk import AraraClient

client = AraraClient(
    api_key=os.environ["ARARA_API_KEY"],  # ou omita: o SDK lê ARARA_API_KEY do ambiente
    base_url="https://api.ararahq.com",   # opcional, este é o default
    timeout=30.0,                         # opcional, em segundos
    max_retries=3,                        # opcional; 0 desliga as retentativas
)

# Fecha as conexões ao sair
with AraraClient() as client:
    ...
```

Todas as opções também podem vir do ambiente: `ARARA_API_KEY`, `ARARA_BASE_URL`, `ARARA_TIMEOUT`, `ARARA_MAX_RETRIES`.

## Enviar a primeira mensagem

Você precisa de plano assinado, número conectado, um template aprovado e saldo. Veja [Antes de começar](/quickstart#antes-de-começar). Troque `pedido_confirmado` pelo nome do seu template.

Salve como `enviar.py`:

```python theme={null}
from arara_api_sdk import AraraClient
from arara_api_sdk.models.message import SendMessageRequest

with AraraClient() as client:  # lê ARARA_API_KEY do ambiente
    response = client.messages.send(SendMessageRequest(
        receiver="+5511999999999",
        template_name="pedido_confirmado",
        template_variables=["João", "12345"],
    ), idempotency_key="pedido-12345")  # id do pedido no seu sistema

print(response.id)      # ara_msg_...
print(response.status)  # status inicial da mensagem
```

Rode:

```bash theme={null}
ARARA_API_KEY=ara_live_... python enviar.py
```

O `receiver` aceita `+5511999999999`, `whatsapp:+5511999999999` ou só os dígitos. O `idempotency_key` é opcional: sem ele o SDK gera um por chamada e o reaproveita nas retentativas. Passe o seu para evitar duplicidade entre processos (veja a seção Retentativas e idempotência).

Com variáveis no corpo do template e mídia no header:

```python theme={null}
client.messages.send(SendMessageRequest(
    receiver="+5511999999999",
    template_name="confirmacao_pedido",
    template_variables=[
        "https://meusite.com/nota/123.pdf",  # header (mídia opcional)
        "João",                               # {{1}}
        "ARA-12345",                          # {{2}}
    ],
))
```

`SendMessageRequest` rejeita variáveis com quebra de linha ou tab (`ValidationError` do Pydantic, antes de chamar a API).

Para responder dentro da janela de 24h, mande `body` em vez de `template_name`:

```python theme={null}
client.messages.send(SendMessageRequest(
    receiver="+5511999999999",
    body="Oi! Vi sua dúvida e respondo já já.",
))
```

Para consultar depois, use `client.messages.get(id)`. Ele só devolve mensagens do mesmo usuário dono da chave (ver **Limitações conhecidas**). Para enviar o mesmo template a até 1.000 números, use `client.messages.send_batch(BatchMessageRequest(...))` e `client.messages.list_by_batch(batch_id)`. Em produção, acompanhe o status pelo webhook `message.status_updated`.

### Async

Todo método síncrono tem uma variante `_async` com a mesma assinatura.

```python theme={null}
import asyncio
from arara_api_sdk import AraraClient
from arara_api_sdk.models.message import SendMessageRequest

async def main():
    async with AraraClient() as client:
        return await asyncio.gather(*[
            client.messages.send_async(SendMessageRequest(
                receiver=phone,
                template_name="lembrete_aula",
                template_variables=["João"],
            ), idempotency_key=f"lembrete-{phone}")
            for phone in ["+5511999990001", "+5511999990002"]
        ])

results = asyncio.run(main())
```

## Templates

`get`, `get_status` e `delete` recebem o **id** do template (UUID).

```python theme={null}
from uuid import UUID

template_id = UUID("7f1c2e9a-3b4d-4c5e-8f60-1a2b3c4d5e6f")

page = client.templates.list(name="pedido_confirmado")  # { data, pagination }
print([t.name for t in page.data], page.pagination.total_elements)

status = client.templates.get_status(template_id)
print(status.status, status.rejection_reason)

template = client.templates.get(template_id)
print(template.name, template.provider_status)

client.templates.delete(template_id)
```

Para criar um template, use `client.templates.create(CreateTemplateRequest(name=..., category=..., body=...))` (importe de `arara_api_sdk.models.template`). `client.templates.analytics(template_id, period="30d")` devolve as métricas de entrega e leitura.

## Tratar erros

```python theme={null}
from arara_api_sdk import (
    AraraAuthError,
    AraraError,
    AraraRateLimitError,
    AraraUnprocessableError,
)

try:
    client.messages.send(SendMessageRequest(receiver="+5511999999999", body="oi"))
except AraraUnprocessableError as e:
    print(e.code, e.message)  # ex.: INVALID_RECIPIENT
except AraraRateLimitError as e:
    print(f"Limite atingido, tente em {e.retry_after or 1}s")  # depois das retentativas
except AraraAuthError:
    print("Chave de API inválida, revogada ou expirada")
except AraraError as e:
    print(e.status_code, e.code, e.message, e.details)
```

| Exceção | Quando |
| - | - |
| `AraraValidationError` | 400 |
| `AraraAuthError` | 401 |
| `AraraForbiddenError` | 403 sem envelope de erro (header `Authorization` ausente ou chave sem permissão para a rota) |
| `AraraApiError` | 403 com envelope, como `ACCOUNT_NOT_ACTIVATED` e `PLAN_LIMIT_REACHED` |
| `AraraPlanFeatureLockedError` | 403 `PLAN_FEATURE_LOCKED` (`feature`, `current_plan`, `upgrade_to`) |
| `AraraResourceNotFoundError` | 404 |
| `AraraUnprocessableError` | 422 |
| `AraraRateLimitError` | 429 (`retry_after` em segundos) |
| `AraraServerError` | 5xx |
| `AraraConnectionError` / `AraraTimeoutError` | falha de rede / timeout |
| `AraraError` | qualquer outro status, como 402 e 409 (base de todas as anteriores) |

Toda exceção expõe `status_code`, `response_body` (o JSON da resposta), `message`, `code`, `details` e `retry_after`. `code` e `details` vêm do envelope de erro `{"error": {"code", "message", "details"}}` e ficam vazios (`None` e `{}`) quando o corpo não segue o envelope, o caso de 401 e 403 de autenticação. A API responde 401 para chave inválida, revogada ou expirada, e 403 quando o header `Authorization` está ausente ou a chave não tem permissão para a rota. Lista de códigos em [Erros](/erros).

## Retentativas e idempotência

O SDK retenta 5xx, 429, erro de conexão e timeout, com espera exponencial entre 2s e 10s, até `max_retries` vezes. Em 429 ele espera o `Retry-After` da resposta; se o valor passar de 60s, não retenta e levanta `AraraRateLimitError`.

Todo envio (`messages.send`, `messages.send_batch` e `campaigns.create`) manda o header `Idempotency-Key`. Sem `idempotency_key`, o SDK gera um UUID por chamada e repete o mesmo valor em cada retentativa, então um timeout depois de a API aceitar o pedido não duplica a mensagem. Passe o seu para dedupe entre processos e reinícios:

```python theme={null}
client.messages.send(request, idempotency_key=f"pedido-{pedido.id}")
```

Repetir a mesma chave em `messages.send` devolve a mensagem original, mesmo com corpo diferente: a API não compara o corpo. Chamadas `POST`/`PATCH` sem `Idempotency-Key` nunca são retentadas. Para desligar as retentativas, use `max_retries=0` ou `ARARA_MAX_RETRIES=0`.

## Webhooks

O SDK não tem helper de webhook. Valide a assinatura antes de processar, com o corpo cru da requisição (os bytes exatamente como chegaram, antes de qualquer parse). A string assinada é `"{X-Arara-Timestamp}.{corpo cru}"`:

```python theme={null}
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 300

def is_valid_arara(raw_body: bytes, headers: dict, secret: str) -> bool:
    timestamp = headers.get("X-Arara-Timestamp")
    signature = headers.get("X-Arara-Signature")
    if not isinstance(timestamp, str) or not isinstance(signature, str):
        return False
    if not signature.startswith("sha256=") or not timestamp.isdigit():
        return False
    if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
        return False
    expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature[len("sha256="):])
```

Use `hmac.compare_digest`, nunca `==`. Depois de validar, trate o evento pelo campo `event`. Detalhes e catálogo em [Webhooks](/webhooks).

A URL do webhook é cadastrada pelo [painel](https://app.ararahq.com/dashboard/webhooks). A API não aceita essa configuração com chave de API.

## Recursos disponíveis

Todo método tem a variante `_async` (por exemplo `send_async`).

| Recurso | Acesso | Métodos |
| - | - | - |
| Messages | `client.messages` | `send`, `send_batch`, `get`, `list_by_batch` |
| Templates | `client.templates` | `create`, `list`, `get`, `get_status`, `delete`, `analytics` |
| Campaigns | `client.campaigns` | `create`, `list`, `get`, `cancel` |
| Contacts | `client.contacts` | `list`, `import_batch`, `stats`, `reactivation_candidates`, `list_tags`, `get`, `update`, `messages` |
| Conversations | `client.conversations` | `list`, `lead_stats`, `messages`, `reply`, `update_status`, `window_status` |
| Numbers | `client.numbers` | `list`, `update`, `delete`, `request`, `list_requests`, `sync`, `warming` |
| Opt-outs | `client.opt_outs` | `list`, `create`, `get`, `delete` |
| Smart links | `client.smart_links` | `create`, `update`, `list`, `stats` |
| Wallet | `client.wallet` | `transactions`, `get_auto_recharge`, `update_auto_recharge` |
| Auth | `client.auth` | `me` |

Contacts, conversations, wallet e `auth.me` exigem chave de API com permissão ADMIN.

## Limitações conhecidas

| Item | Comportamento |
| - | - |
| `messages.get` | A API só devolve mensagens enviadas pelo mesmo usuário dono da chave; mensagem de outro membro da organização responde 403 com corpo vazio. |
| `media_url` em `SendMessageRequest` | Descontinuado. Mande a mídia do header como primeiro item de `template_variables`. |
| Webhooks | O SDK não configura webhook. `organizations`, `users`, `brain` e `payments` não existem na 2.0.0. |

## Próximos passos

<CardGroup cols={2}>
  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Endpoints REST por baixo do SDK.
  </Card>

  <Card title="Webhooks" icon="bell" href="/webhooks">
    Eventos, payload e validação de assinatura.
  </Card>
</CardGroup>

Código-fonte: [github.com/ararahq/arara-python-sdk](https://github.com/ararahq/arara-python-sdk) (MIT).


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