Não existe pacote para instalar#
A API é REST com JSON: qualquer cliente HTTP serve. O Throne não publica pacote no npm, Packagist, PyPI ou outro repositório. Pacotes com nomes parecidos com "throne" não são nossos: não instale.
Abaixo há um cliente mínimo, pronto para copiar, em cada linguagem. Ele já:
- monta a autenticação e manda
Accept: application/json; - usa timeout (20 segundos por padrão; passe 60 segundos na criação de checkout de cartão);
- aceita uma
Idempotency-Keysua nosPOST, para você repetir com a mesma chave depois de um timeout; - transforma erros em exceção com o código
error, o status, oretryablee oRetry-After. Sem resposta (falha de conexão ou timeout), o status é0.
final class ThroneError extends RuntimeException
{
public function __construct(
public readonly int $status, // 0 = sem resposta (conexão caiu ou timeout)
public readonly string $error,
string $message,
public readonly ?bool $retryable = null,
public readonly ?int $retryAfter = null,
) {
parent::__construct("{$error}: {$message}", $status);
}
}
final class Throne
{
public function __construct(
private string $pk,
private string $sk,
private string $base = 'https://app.thronecorporate.com/api/v1',
) {}
public function get(string $path, int $timeout = 20): array
{
return $this->request('GET', $path, null, null, $timeout);
}
public function post(string $path, array $body, ?string $idempotencyKey = null, int $timeout = 20): array
{
return $this->request('POST', $path, $body, $idempotencyKey ?? bin2hex(random_bytes(16)), $timeout);
}
private function request(string $method, string $path, ?array $body, ?string $key, int $timeout): array
{
$headers = [
"Authorization: Bearer {$this->pk}:{$this->sk}",
'Accept: application/json',
'Content-Type: application/json',
];
if ($key !== null) {
$headers[] = "Idempotency-Key: {$key}";
}
$retryAfter = null;
$ch = curl_init($this->base . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_HEADERFUNCTION => function ($ch, string $line) use (&$retryAfter): int {
if (stripos($line, 'Retry-After:') === 0) {
$retryAfter = (int) trim(substr($line, 12));
}
return strlen($line);
},
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$raw = curl_exec($ch);
if ($raw === false) {
// Sem resposta: repita com a MESMA Idempotency-Key.
throw new ThroneError(0, 'network', curl_error($ch));
}
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$json = json_decode((string) $raw, true) ?? [];
if ($status >= 400) {
throw new ThroneError(
$status,
$json['error'] ?? 'http_' . $status,
$json['message'] ?? '',
$json['retryable'] ?? null,
$retryAfter,
);
}
return $json;
}
}
$throne = new Throne(getenv('THRONE_PK'), getenv('THRONE_SK'));
// Pix
$pix = $throne->post('/transactions', [
'payment_method' => 'pix',
'amount_cents' => 4990,
'customer' => ['name' => 'Maria Souza', 'document' => '12345678909', 'email' => 'maria@exemplo.com'],
]);
// Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout.
$chave = bin2hex(random_bytes(16));
$checkout = $throne->post('/elements/checkout', ['amount_cents' => 4990, 'currency' => 'usd'], $chave, 60);import { randomUUID } from 'node:crypto';
export class ThroneError extends Error {
constructor(status, code, message, retryable = null, retryAfter = null) {
super(`${code}: ${message}`);
this.status = status; // 0 = sem resposta (conexão caiu ou timeout)
this.code = code;
this.retryable = retryable;
this.retryAfter = retryAfter;
}
}
export function throneClient({ pk, sk, base = 'https://app.thronecorporate.com/api/v1' }) {
async function request(method, path, body, idempotencyKey, timeoutMs = 20000) {
const headers = {
Authorization: `Bearer ${pk}:${sk}`,
Accept: 'application/json',
'Content-Type': 'application/json',
};
if (method === 'POST') headers['Idempotency-Key'] = idempotencyKey ?? randomUUID();
let res;
try {
res = await fetch(base + path, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(timeoutMs),
});
} catch (cause) {
// sem resposta: repita com a MESMA Idempotency-Key
throw new ThroneError(0, 'network', cause.message);
}
const json = await res.json().catch(() => ({}));
if (!res.ok) {
throw new ThroneError(
res.status,
json.error ?? `http_${res.status}`,
json.message ?? '',
json.retryable ?? null,
Number(res.headers.get('Retry-After')) || null,
);
}
return json;
}
return {
get: (path, timeoutMs) => request('GET', path, undefined, undefined, timeoutMs),
post: (path, body, idempotencyKey, timeoutMs) => request('POST', path, body, idempotencyKey, timeoutMs),
};
}
const throne = throneClient({ pk: process.env.THRONE_PK, sk: process.env.THRONE_SK });
// Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout.
const chave = randomUUID();
const checkout = await throne.post('/elements/checkout', { amount_cents: 4990, currency: 'usd' }, chave, 60000);import uuid
import requests
class ThroneError(Exception):
def __init__(self, status, code, message, retryable=None, retry_after=None):
super().__init__(f"{code}: {message}")
self.status = status # 0 = sem resposta (conexão caiu ou timeout)
self.code = code
self.retryable = retryable
self.retry_after = retry_after
class Throne:
def __init__(self, pk, sk, base="https://app.thronecorporate.com/api/v1"):
self.base = base
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {pk}:{sk}",
"Accept": "application/json",
})
def _request(self, method, path, body=None, idempotency_key=None, timeout=20):
headers = {}
if method == "POST":
headers["Idempotency-Key"] = idempotency_key or str(uuid.uuid4())
try:
r = self.session.request(method, self.base + path, json=body, headers=headers, timeout=timeout)
except requests.RequestException as e:
# sem resposta: repita com a MESMA Idempotency-Key
raise ThroneError(0, "network", str(e))
try:
data = r.json()
except ValueError:
data = {}
if r.status_code >= 400:
retry_after = r.headers.get("Retry-After")
raise ThroneError(
r.status_code,
data.get("error", f"http_{r.status_code}"),
data.get("message", ""),
data.get("retryable"),
int(retry_after) if retry_after and retry_after.isdigit() else None,
)
return data
def get(self, path, timeout=20):
return self._request("GET", path, timeout=timeout)
def post(self, path, body, idempotency_key=None, timeout=20):
return self._request("POST", path, body, idempotency_key, timeout)
throne = Throne(pk="pk_live_...", sk="sk_live_...")
saldo = throne.get("/balance")
# Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout.
chave = str(uuid.uuid4())
checkout = throne.post("/elements/checkout", {"amount_cents": 4990, "currency": "usd"}, chave, timeout=60)Repetir com segurança#
Com os clientes acima, a decisão fica assim:
| A exceção traz | O que fazer |
|---|---|
status 0 (sem resposta) |
Repita com a mesma Idempotency-Key e o mesmo corpo. Se a primeira chegou, você recebe a resposta original, sem duplicar nada |
502, 503 ou 504 com código começando em http_ |
Página de erro do servidor, sem o JSON da API. Trate como sem resposta: repita com a mesma chave |
409 idempotency_key_processing |
Espere alguns segundos e repita com a mesma chave. Se continuar por mais de 2 minutos, confira no painel se algo foi criado antes de usar uma chave nova |
429, ou 503 com o código da API |
Espere o retryAfter e repita com uma chave nova, poucas vezes, aumentando a espera (recuo) |
502 com o código da API e retryable verdadeiro ou ausente (o 502 do Pix não traz retryable) |
Repita com uma chave nova |
502 com retryable falso |
Não repita igual |
500 |
Algo pode ter sido criado. Não use chave nova antes de conferir no painel. Em POST /elements/checkout, repita antes com a mesma chave: se nada foi criado, a chamada roda de novo |
Qualquer outro 4xx |
Corrija o que for preciso e repita com uma chave nova |
Por isso os clientes aceitam uma Idempotency-Key sua: gere a chave antes, guarde junto do pedido e reuse na repetição. A tabela completa está em como repetir com segurança.
Validar webhooks#
Os exemplos de validação da assinatura X-Throne-Signature em PHP, Node.js, Python e Go estão em Webhooks.