Datas e dinheiro
Dois formatos que aparecem em quase todo recurso, e que erram fácil quando o integrador supõe em vez de conferir.
Data com hora sai em ISO 8601, sempre em UTC, com o Z no fim:
2026-09-16T14:30:00ZCampo que guarda só data, sem hora, sai assim:
2026-09-16O mesmo formato vale nos dois sentidos: o que você manda numa data segue a mesma regra do que você recebe, inclusive nos filtros de paginação.
Campo de data e hora sai sempre assim: created_at e updated_at de qualquer recurso, key.expires_at em GET /public/v1/me, next_follow_up e last_interaction_at no cliente, due_date, started_at e completed_at na tarefa, starts_at e ends_at no compromisso da agenda, scheduled_start, scheduled_end, started_at e completed_at na ordem de serviço, valid_until, estimated_start_date, estimated_completion_date, approved_at e rejected_at no orçamento, e estimated_delivery_date e canceled_at na venda (e canceled_at e created_at em cada recebimento dela). O movimento do extrato de estoque só tem created_at, porque nunca muda depois de gravado. Campo de só data usa YYYY-MM-DD, como o birth_date do cliente e o paid_at e o due_date do plano de pagamento da conversão de orçamento em venda, e na venda o sale_date, o due_date e o paid_date de cada parcela, o paid_at de cada recebimento e as datas dos filtros sale_date_after e sale_date_before. No financeiro, transaction_date, due_date e payment_date do lançamento são só data, nos dois sentidos, como os filtros due_after e due_before.
UTC na API, fuso da empresa na tela
Seção intitulada “UTC na API, fuso da empresa na tela”A API não devolve horário local. Ela devolve UTC e diz qual é o fuso da empresa, em organization.timezone na resposta de GET /public/v1/me:
{ "organization": { "timezone": "America/Sao_Paulo", "currency": "BRL" }}O painel mostra as mesmas datas convertidas para esse fuso. Então o mesmo registro aparece como 2026-09-16T14:30:00Z na API e como 16/09/2026 11:30 na tela, e os dois estão certos.
Se o seu sistema mostra data para uma pessoa, converta de UTC para organization.timezone na hora de exibir, e nunca guarde a data já convertida: guardar em UTC é o que faz a conta de horário de verão e de mudança de fuso continuar certa depois.
Dinheiro
Seção intitulada “Dinheiro”Dinheiro é inteiro, em centavos, e o nome do campo diz isso com todas as letras:
{ "total_cents": 150000 }150000 são R$ 1.500,00. O nome sempre termina em _cents, então não existe campo de dinheiro em que você precise adivinhar a unidade.
A moeda vem em organization.currency, na resposta de GET /public/v1/me. A API não converte moeda: o inteiro está na moeda que esse campo diz.
O primeiro campo de dinheiro no ar é o price_cents do compromisso da agenda: price_cents: 15000 são R$ 150,00, e null quer dizer compromisso sem valor. A ordem de serviço também traz dinheiro em centavos: subtotal_cents, discount_cents, additional_costs_cents e total_cents, mais unit_price_cents e total_cents em cada item. O orçamento também: subtotal_cents, discount_cents, additional_costs_cents, total_cents e recurring_total_cents (a mensalidade, que nunca soma com o total), mais unit_price_cents e total_cents em cada item, e entry_amount_cents e amount_cents no plano de pagamento da conversão em venda. A venda traz subtotal_cents, discount_cents, shipping_cents, additional_costs_cents, total_cents e paid_cents, mais unit_price_cents, original_price_cents e total_price_cents em cada item, amount_cents, paid_cents e remaining_cents em cada parcela e amount_cents em cada recebimento; o recebimento e o reagendamento também recebem amount_cents. O produto traz price_cents e promotional_price_cents (e o mesmo par em cada variação), e o serviço traz base_price_cents. A alíquota de ISS do serviço (iss_rate) é fração e sai como número: 0.05 quer dizer 5%. O percentual de desconto (discount_percentage) não é dinheiro e sai como número: 10 quer dizer 10%. O lançamento do financeiro traz amount_cents e, no parcelamento, original_amount_cents; o parcelamento recebe total_amount_cents ou installment_amount_cents. A conta traz opening_balance_cents, current_balance_cents e, no cartão, credit_limit_cents, credit_used_cents e credit_available_cents; o alert_percentage da conta é percentual e sai como número. Clientes, endereços, leads, tarefas e equipe não têm campo de dinheiro. A mesma regra vale para os recursos que chegam nas próximas fases.
Por que centavos
Seção intitulada “Por que centavos”Número decimal com ponto flutuante perde centavo. Não é teoria: em qualquer linguagem que use ponto flutuante binário, 0.1 + 0.2 não dá 0.3, e uma soma de mil itens acumula a diferença até o total fechar errado.
Com inteiro isso não acontece. Somar, subtrair e multiplicar por quantidade são contas exatas. A divisão continua pedindo cuidado, porque é onde o arredondamento entra, e aí você decide a regra em vez de descobrir o resultado depois.
Converter centavos
Seção intitulada “Converter centavos”Os exemplos imprimem o valor formatado e voltam de texto para centavos com aritmética inteira.
CENTS=150000
# cURL só transporta a resposta. A conta é do shell, com inteiro.printf 'R$ %d,%02d\n' "$((CENTS / 100))" "$((CENTS % 100))"
# De volta, de "1500,00" para centavos, sem passar por decimal.VALOR="1500,00"INTEIRA=${VALOR%%,*}FRACAO=${VALOR##*,}printf '%d\n' "$((INTEIRA * 100 + 10#$FRACAO))"Rode com bash. O 10# obriga a leitura em base 10, senão 08 e 09 viram erro de octal. O separador de milhar fica de fora: agrupamento por idioma é trabalho da linguagem que monta a tela, não do shell.
const cents = 150000;
const texto = (cents / 100).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL',});console.log(texto);
// De volta para centavos, com inteiro: sem multiplicar por 100 em decimal.function paraCentavos(valor) { const [inteira, fracao = '0'] = valor.replace(/[^\d,]/g, '').split(','); return Number(inteira) * 100 + Number(fracao.padEnd(2, '0').slice(0, 2));}
console.log(paraCentavos('1.500,00'));Rode com node dinheiro.mjs. A divisão por 100 aparece uma vez só, na formatação.
<?php
$cents = 150000;
echo 'R$ ' . number_format(intdiv($cents, 100), 0, ',', '.') . ',' . str_pad((string) ($cents % 100), 2, '0', STR_PAD_LEFT), PHP_EOL;
function paraCentavos(string $valor): int{ $limpo = preg_replace('/[^\d,]/', '', $valor); [$inteira, $fracao] = array_pad(explode(',', $limpo), 2, '0'); return ((int) $inteira) * 100 + (int) str_pad(substr($fracao, 0, 2), 2, '0');}
echo paraCentavos('1.500,00'), PHP_EOL;Rode com php dinheiro.php. intdiv e % são inteiros: nenhum ponto flutuante entra na conta.
from decimal import Decimal
cents = 150_000
inteira, fracao = divmod(cents, 100)milhar = f"{inteira:,}".replace(",", ".")print(f"R$ {milhar},{fracao:02d}")
def para_centavos(valor: str) -> int: limpo = valor.replace(".", "").replace(",", ".") return int(Decimal(limpo) * 100)
print(para_centavos("1.500,00"))Rode com python3 dinheiro.py. Decimal em vez de float: float("1500.10") * 100 dá 150009.99999999999.
Próximo passo
Seção intitulada “Próximo passo”- Paginação: os filtros de data das listas usam este mesmo formato.
- Versões e changelog: por que campo novo numa resposta não quebra a sua integração.