"""API v1 do Rumo CRM com Python + requests (sem SDK). Cria um contato, abre um negócio no funil padrão, move de etapa e lista os negócios abertos percorrendo todas as páginas. Rode com: pip install requests RUMO_TOKEN=teha_pat_... python exemplo.py """ import os import time import uuid import requests BASE = "https://rumocrm.com/api/v1" TOKEN = os.environ.get("RUMO_TOKEN") if not TOKEN: raise SystemExit("Defina RUMO_TOKEN com um token criado em Configurações → API e integrações.") sessao = requests.Session() sessao.headers.update({"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"}) class ErroRumo(Exception): def __init__(self, status: int, erro: dict): super().__init__(f"{status} {erro.get('code')}: {erro.get('message')} (requestId {erro.get('requestId')})") self.status = status self.code = erro.get("code") self.param = erro.get("param") self.request_id = erro.get("requestId") def pedir(metodo: str, caminho: str, *, json=None, params=None, tentativas: int = 3) -> dict: """Faz o pedido e repete em 429/5xx. POST/PATCH/DELETE levam a mesma Idempotency-Key em todas as tentativas: a repetição nunca duplica a escrita.""" cabecalhos = {"Idempotency-Key": str(uuid.uuid4())} if metodo in ("POST", "PATCH", "DELETE") else {} for tentativa in range(tentativas): resposta = sessao.request(metodo, BASE + caminho, json=json, params=params, headers=cabecalhos, timeout=30) if resposta.ok: return resposta.json() if tentativa < tentativas - 1 and (resposta.status_code == 429 or resposta.status_code >= 500): time.sleep(int(resposta.headers.get("Retry-After", 2 ** tentativa))) continue try: erro = resposta.json()["error"] except (ValueError, KeyError): erro = {"code": "unexpected_response", "message": resposta.text[:200]} raise ErroRumo(resposta.status_code, erro) raise AssertionError("inalcançável") def todos(caminho: str, **filtros): """Devolve todos os itens de uma lista, seguindo nextCursor até hasMore virar False.""" params = {**filtros, "limit": 100} while True: pagina = pedir("GET", caminho, params=params) yield from pagina["data"] if not pagina["hasMore"]: return params["cursor"] = pagina["nextCursor"] def main() -> None: # Upsert: o mesmo telefone devolve o mesmo contato, nunca duplica. contato = pedir("POST", "/contacts", json={"name": "Maria Souza", "phone": "(15) 99807-3400", "tags": ["site"]}) funis = pedir("GET", "/pipelines")["data"] funil = next((f for f in funis if f["isDefault"]), funis[0]) etapas_abertas = [e for e in pedir("GET", f"/pipelines/{funil['id']}/stages")["data"] if e["type"] == "open"] # Valor em centavos: 150000 = R$ 1.500,00. negocio = pedir( "POST", "/deals", json={"title": f"Proposta - {contato['name']}", "pipelineId": funil["id"], "contactId": contato["id"], "valueCents": 150000}, ) if len(etapas_abertas) > 1: pedir("POST", f"/deals/{negocio['id']}/move", json={"stageId": etapas_abertas[1]["id"]}) for aberto in todos("/deals", pipelineId=funil["id"], status="open"): valor = "sem valor" if aberto["valueCents"] is None else f"R$ {aberto['valueCents'] / 100:.2f}" print(aberto["id"], aberto["title"], valor) if __name__ == "__main__": try: main() except ErroRumo as erro: raise SystemExit(str(erro))