Metadata-Version: 2.5
Name: cronos_flow_sdk
Version: 0.1.0
Summary: SDK oficial da API publica do CronosFlow: envio de WhatsApp com ritmo seguro, idempotencia e repeticao controlada.
Project-URL: Homepage, https://cronosflow.sophialabs.com.br
Project-URL: Documentation, https://cronosflow.sophialabs.com.br/docs/api
Project-URL: Repository, https://github.com/SophiaLab/cronos_python_sdk
Project-URL: Changelog, https://github.com/SophiaLab/cronos_python_sdk/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/SophiaLab/cronos_python_sdk/issues
Author-email: Team Sophia <contato@sophialabs.com.br>
Maintainer-email: SophiaLabs <contato@sophialabs.com.br>
License: Proprietary
License-File: LICENSE
Keywords: api,cronosflow,mensageria,sdk,whatsapp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Chat
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.27
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# CronosFlow SDK para Python

SDK oficial da [API pública do CronosFlow](https://cronosflow.sophialabs.com.br/docs/api):
envio de WhatsApp a partir do seu servidor, com o ritmo que protege o número.

```bash
pip install cronos-flow-sdk
```

Instala como `cronos-flow-sdk` e importa como `cronosflow`.


```python
import os
from cronosflow import CronosClient

with CronosClient(api_key=os.environ["CRONOS_API_KEY"]) as cronos:
    if not cronos.clients.status("1").logged_in:
        raise SystemExit("número desconectado: leia o QR code na plataforma")

    cronos.messages.text("1", "+5511999999999", "Seu pedido saiu para entrega.")
```

## Por que um SDK, e não um `requests.post`

A API tem 18 rotas e um `curl` resolve qualquer uma delas. O que não cabe num
`curl` é o que faz uma integração sobreviver ao segundo mês:

| | |
|---|---|
| **Ritmo entre envios** | 35 a 45 segundos entre mensagens **do mesmo número**, sorteado. É o mesmo padrão que a plataforma usa no disparo. Rajada pelo mesmo chip é o padrão que o WhatsApp usa para bloquear, e conta bloqueada não volta por reclamação. |
| **Idempotência** | Uma `Idempotency-Key` por mensagem, gerada antes da primeira tentativa e repetida em todas. Falha de rede deixa de virar mensagem duplicada. |
| **Repetição que não piora as coisas** | Espera crescente só em 429 e 5xx. Um 422 de validação não é repetido: repetir não conserta e ainda gasta o seu teto por minuto. |
| **Recusa antes da rede** | Destino fora do E.164, áudio que não tocaria, arquivo acima do teto, enquete com opção repetida: tudo isso falha na sua máquina, sem gastar rede nem cota do plano. |
| **Erro com nome** | O mesmo `403` da API é "chave inválida", "conta bloqueada" e "cota do dia acabou" — três reações diferentes. Aqui são três exceções diferentes. |

## O ritmo de envio

É a parte que mais importa, então vale a explicação completa.

O teto da API é 160 mensagens por minuto na rota de texto. Esse é o limite do
**servidor**, não um ritmo seguro para o **seu número**. O SDK nasce com a
mesma janela que a plataforma aplica no disparo:

```python
from cronosflow import CronosClient, SendPolicy

cronos = CronosClient(
    api_key=...,
    policy=SendPolicy(min_interval=35, max_interval=45),  # o padrão
)
```

Três detalhes do desenho:

- **a espera é por número.** Quem opera cinco chips manda pelos cinco sem um
  esperar pelo outro; o que não pode é o mesmo chip disparar em sequência;
- **o intervalo é sorteado**, não fixo. Mensagem a cada 40 segundos cravados é
  tão artificial quanto rajada;
- **a primeira sai na hora.** A espera é *entre* envios.

Para uma lista, `send_many` já cuida do ritmo e não para no primeiro erro:

```python
from cronosflow import CronosError

for destino, resultado in cronos.messages.send_many("1", lista, "Promoção de hoje"):
    if isinstance(resultado, CronosError):
        registrar_falha(destino, resultado)
```

Dá para desligar o ritmo (`SendPolicy(min_interval=0, max_interval=0)`), e só
faz sentido quando **outra coisa** já controla a cadência — uma fila com
agendamento, por exemplo. Sem ritmo nenhum, o bloqueio do chip é questão de
tempo.

## O que dá para fazer

```python
# Números da conta
cronos.clients.list(q="comercial")
cronos.clients.create(name="Comercial", number="+5511999990000")
cronos.clients.update(1, name="Comercial (SP)")
cronos.clients.delete(1)
cronos.clients.status(1)

# Conectar sem abrir a plataforma
for evento in cronos.clients.qr_events(1):
    if evento.qr:
        mostrar_qr(evento.qr)
    if evento.is_logged_in:
        break

# Mensagens
cronos.messages.text(1, "+5511999999999", "Oi")
cronos.messages.poll(1, "+5511999999999", "Qual horário?", ["Manhã", "Tarde"])
cronos.messages.image(1, "+5511999999999", "promo.png", caption="Promoção")
cronos.messages.audio(1, "+5511999999999", "recado.ogg")
cronos.messages.video(1, "+5511999999999", "clipe.mp4")
cronos.messages.document(1, "+5511999999999", "boleto.pdf")
```

Áudio precisa ser `.ogg` (Opus) ou `.mp3`, e vídeo precisa ser `.mp4` — são os
formatos que o WhatsApp reproduz. O SDK confere isso **pelos bytes do arquivo**,
antes de subir, e a mensagem de erro traz o comando do ffmpeg. Qualquer outro
formato vai como anexo por `document()`.

## Erros

```python
from cronosflow import (
    CronosError,            # a base de tudo
    AuthenticationError,    # 401: chave não enviada
    SubscriptionRequired,   # 402: assinatura vencida ou não ativada
    ApiKeyRejected,         # 403: chave inválida, revogada ou vencida
    AccountBlocked,         # 403: conta bloqueada
    DailyLimitReached,      # 403: teto de envios por dia do plano
    NotFoundError,          # 404: client_id não é seu
    IdempotencyInFlight,    # 409: a mesma chave ainda está em voo
    ValidationError,        # 422: pedido inválido (da API ou do próprio SDK)
    UnsupportedMedia,       # 415: formato que não reproduziria
    PayloadTooLarge,        # 413: arquivo acima do teto
    RateLimited,            # 429: teto por minuto, já repetido pelo SDK
    SendFailed,             # 502: o WhatsApp não aceitou
)
```

`DailyLimitReached` é o único que passa sozinho com o tempo: a cota do plano
vira à meia-noite UTC, que são 21h no horário de Brasília. Os outros exigem
alguma ação.

## Segurança

- **a chave vale como senha da conta**: ela envia mensagem, cria e apaga
  cliente. Leia do ambiente do servidor; nunca escreva em código que roda no
  navegador nem em repositório;
- o SDK **recusa `base_url` sem https** (`allow_insecure=True` libera, e serve
  só para desenvolvimento local);
- a chave **não aparece** em `repr`, em log ou em mensagem de erro — só o
  prefixo;
- toda requisição tem timeout; nenhuma fica pendurada para sempre.

## Compatibilidade

Python 3.9 ou mais novo. Uma dependência só: `httpx`.

## Quem mantém

Team Sophia, da [SophiaLabs](https://sophialabs.com.br). Dúvida de uso vai em
[issue](https://github.com/SophiaLab/cronos_python_sdk/issues); falha de
segurança vai para contato@sophialabs.com.br, nunca em issue pública
([SECURITY.md](SECURITY.md)).

## Licença

Veja [LICENSE](LICENSE).
