Skip to content

Dates and money

Two formats that show up in almost every resource, and that go wrong easily when the integrator assumes instead of checking.

A date with time comes out in ISO 8601, always in UTC, with the Z at the end:

2026-09-16T14:30:00Z

A field that holds a date only, with no time, comes out like this:

2026-09-16

The same format works both ways: what you send in a date follows the same rule as what you receive, including the filters in Pagination.

Every date and time field comes out this way: created_at and updated_at on any resource, key.expires_at in GET /public/v1/me, next_follow_up and last_interaction_at on a client, due_date, started_at and completed_at on a task, starts_at and ends_at on an appointment, scheduled_start, scheduled_end, started_at and completed_at on a service order, valid_until, estimated_start_date, estimated_completion_date, approved_at and rejected_at on a quote, and estimated_delivery_date and canceled_at on a sale (and canceled_at and created_at on each of its payments). A stock ledger movement has only created_at, because it never changes after it is recorded. A date-only field uses YYYY-MM-DD, like the client birth_date and the paid_at and due_date of the payment plan when converting a quote to a sale, and on a sale the sale_date, the due_date and paid_date of each installment, the paid_at of each payment and the dates of the sale_date_after and sale_date_before filters. In finance, the entry transaction_date, due_date and payment_date are date-only, both ways, like the due_after and due_before filters.

UTC in the API, company time zone on screen

Section titled “UTC in the API, company time zone on screen”

The API does not return local time. It returns UTC and tells you the company time zone, in organization.timezone in the GET /public/v1/me response:

{
"organization": {
"timezone": "America/Sao_Paulo",
"currency": "BRL"
}
}

The panel shows the same dates converted to that time zone. So the same record shows up as 2026-09-16T14:30:00Z in the API and as 16/09/2026 11:30 on screen, and both are right.

If your system shows a date to a person, convert from UTC to organization.timezone at display time, and never store the already converted date: storing in UTC is what keeps daylight saving and time zone changes correct afterwards.

Money is an integer, in cents, and the field name spells that out:

{ "total_cents": 150000 }

150000 is BRL 1,500.00. The name always ends in _cents, so there is no money field where you have to guess the unit.

The currency comes in organization.currency, in the GET /public/v1/me response. The API does not convert currency: the integer is in the currency that field says.

The first money field live is price_cents on the appointment: price_cents: 15000 is BRL 150.00, and null means an appointment with no amount. Service orders carry money in cents too: subtotal_cents, discount_cents, additional_costs_cents and total_cents, plus unit_price_cents and total_cents on each item. So do quotes: subtotal_cents, discount_cents, additional_costs_cents, total_cents and recurring_total_cents (the monthly amount, which never adds to the total), plus unit_price_cents and total_cents on each item, and entry_amount_cents and amount_cents in the payment plan when converting to a sale. Sales carry subtotal_cents, discount_cents, shipping_cents, additional_costs_cents, total_cents and paid_cents, plus unit_price_cents, original_price_cents and total_price_cents on each item, amount_cents, paid_cents and remaining_cents on each installment and amount_cents on each payment; recording a payment and rescheduling also take amount_cents. Products carry price_cents and promotional_price_cents (and the same pair on each variation), and services carry base_price_cents. The service ISS rate (iss_rate) is a fraction and comes out as a number: 0.05 means 5%. The discount percentage (discount_percentage) is not money and comes out as a number: 10 means 10%. A finance entry carries amount_cents and, on installments, original_amount_cents; creating installments takes total_amount_cents or installment_amount_cents. An account carries opening_balance_cents, current_balance_cents and, on a credit card, credit_limit_cents, credit_used_cents and credit_available_cents; the account alert_percentage is a percentage and comes out as a number. Clients, addresses, leads, tasks and team have no money field. The same rule applies to the resources arriving in the next phases.

A decimal number in binary floating point loses cents. This is not theory: in any language that uses binary floating point, 0.1 + 0.2 is not 0.3, and a sum over a thousand items accumulates the difference until the total closes wrong.

With integers that does not happen. Adding, subtracting and multiplying by a quantity are exact. Division still needs care, because that is where rounding enters, and there you decide the rule instead of discovering the result later.

The examples print the formatted value and go back from text to cents with integer arithmetic.

Terminal window
CENTS=150000
# cURL only carries the response. The arithmetic is the shell's, in integers.
printf 'BRL %d.%02d\n' "$((CENTS / 100))" "$((CENTS % 100))"
# Back, from "1500.00" to cents, without going through a decimal.
VALUE="1500.00"
WHOLE=${VALUE%%.*}
FRACTION=${VALUE##*.}
printf '%d\n' "$((WHOLE * 100 + 10#$FRACTION))"

Run it with bash. The 10# forces base 10 reading, otherwise 08 and 09 become an octal error. The thousands separator is left out: grouping by language is the job of the language that builds the screen, not of the shell.