Pular para o conteúdo principal

Clientes e snippets

Não publicamos SDK oficial. A API é HTTP + JSON com autenticação Bearer — qualquer cliente HTTP resolve, e você não fica preso a uma dependência nossa.

Cada página da referência traz o snippet pronto em curl, Node.js, Python, PHP, Go e Ruby, com o payload da operação já preenchido. O que segue aqui é o esqueleto reutilizável.

Node.js​

joinotify.js
const BASE_URL = 'https://api.joinotify.com';

export class Joinotify {
constructor(token = process.env.JOINOTIFY_TOKEN) {
this.token = token;
}

async request(method, path, body) {
const response = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});

// Respeita o Retry-After em vez de insistir.
if (response.status === 429) {
const espera = Number(response.headers.get('retry-after') ?? 5);
await new Promise((r) => setTimeout(r, espera * 1000));
return this.request(method, path, body);
}

const payload = await response.json();

if (!response.ok) {
const erro = new Error(payload?.error?.message ?? 'Erro na API do Joinotify');
erro.type = payload?.error?.type;
erro.status = response.status;
erro.meta = payload?.error?.meta;
throw erro;
}

return payload;
}

enviarTexto(to, body, extras = {}) {
return this.request('POST', '/messages', { to, body, ...extras });
}

enviarTemplate(to, name, componentes = [], language = 'pt_BR') {
return this.request('POST', '/messages', {
to,
name,
language,
components: componentes,
});
}

listarTemplates(params = {}) {
const query = new URLSearchParams(params).toString();
return this.request('GET', `/templates${query ? `?${query}` : ''}`);
}
}
const joinotify = new Joinotify();
const { data } = await joinotify.enviarTexto('5541987111527', 'Pedido confirmado!');
console.log(data.messages[0].id);

Python​

joinotify.py
import os
import time

import requests

BASE_URL = "https://api.joinotify.com"


class JoinotifyError(Exception):
def __init__(self, message, *, type=None, status=None, meta=None):
super().__init__(message)
self.type = type
self.status = status
self.meta = meta


class Joinotify:
def __init__(self, token=None):
self.session = requests.Session()
self.session.headers["Authorization"] = f"Bearer {token or os.environ['JOINOTIFY_TOKEN']}"

def request(self, method, path, json=None):
response = self.session.request(method, f"{BASE_URL}{path}", json=json, timeout=30)

if response.status_code == 429:
time.sleep(int(response.headers.get("Retry-After", 5)))
return self.request(method, path, json)

payload = response.json()

if not response.ok:
erro = payload.get("error", {})
raise JoinotifyError(
erro.get("message", "Erro na API do Joinotify"),
type=erro.get("type"),
status=response.status_code,
meta=erro.get("meta"),
)

return payload

def enviar_texto(self, to, body, **extras):
return self.request("POST", "/messages", {"to": to, "body": body, **extras})

PHP​

Joinotify.php
<?php

final class Joinotify
{
private const BASE_URL = 'https://api.joinotify.com';

public function __construct(private readonly string $token) {}

public function request(string $method, string $path, ?array $body = null): array
{
$ch = curl_init(self::BASE_URL . $path);

curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->token,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body ? json_encode($body, JSON_UNESCAPED_UNICODE) : null,
]);

$resposta = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

$payload = json_decode($resposta, true);

if ($status >= 400) {
throw new RuntimeException($payload['error']['message'] ?? 'Erro na API do Joinotify', $status);
}

return $payload;
}

public function enviarTexto(string $to, string $body): array
{
return $this->request('POST', '/messages', ['to' => $to, 'body' => $body]);
}
}

Go​

package joinotify

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)

const baseURL = "https://api.joinotify.com"

type Client struct {
Token string
HTTP *http.Client
}

func New(token string) *Client {
return &Client{Token: token, HTTP: &http.Client{Timeout: 30 * time.Second}}
}

func (c *Client) Do(method, path string, body any, out any) error {
var buf bytes.Buffer
if body != nil {
if err := json.NewEncoder(&buf).Encode(body); err != nil {
return err
}
}

req, err := http.NewRequest(method, baseURL+path, &buf)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+c.Token)
req.Header.Set("Content-Type", "application/json")

res, err := c.HTTP.Do(req)
if err != nil {
return err
}
defer res.Body.Close()

if res.StatusCode >= 400 {
return fmt.Errorf("joinotify: status %d", res.StatusCode)
}

return json.NewDecoder(res.Body).Decode(out)
}

Ruby​

require 'json'
require 'net/http'

class Joinotify
BASE_URL = 'https://api.joinotify.com'

def initialize(token = ENV.fetch('JOINOTIFY_TOKEN'))
@token = token
end

def request(method, path, body = nil)
uri = URI("#{BASE_URL}#{path}")
klass = Net::HTTP.const_get(method.capitalize)

req = klass.new(uri)
req['Authorization'] = "Bearer #{@token}"
req['Content-Type'] = 'application/json'
req.body = body.to_json if body

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
payload = JSON.parse(res.body)

raise payload.dig('error', 'message') || 'Erro na API do Joinotify' unless res.is_a?(Net::HTTPSuccess)

payload
end

def enviar_texto(to, body)
request(:post, '/messages', { to: to, body: body })
end
end

O que todo cliente precisa ter​

Timeout. Sem ele, uma requisição travada segura um worker indefinidamente. 30 segundos cobre com folga o timeout de 30s do proxy.

Tratamento de 429. Respeite o Retry-After. Ver Limites de uso.

Backoff em 5xx. 500, 502 e 504 valem retentativa com espera crescente. 4xx não — repetir vai falhar igual.

O x-request-id nos logs. É a primeira coisa que o suporte pede.

O token fora do código. Variável de ambiente ou cofre de segredos, nunca no repositório.