Build on your quotes, invoices, jobs, scheduling and checkouts.
One REST API over the same records your team works in every day — quotes, invoices, checkouts, jobs and their scheduled visits, customers, products and discounts.
REST over HTTPS, JSON in and out·Bearer token auth·Scoped by companyId
Getting started
1
Create an API user
In the portal, open Settings → Users and add an API user with a nickname and a role. Each API user has its own per-module permissions, so a token can be limited to exactly what the integration needs.
2
Store the token
The token is shown once, at creation, and stored hashed after that. Keep it server-side — it carries account-level access.
3
Send your company ID
Every request must say which company it acts on. Pass companyId in the query string on GET and DELETE, and usually in the body on POST, PUT and PATCH — some write endpoints need it in the query too, so check each endpoint’s parameter table. Find it under Settings → API.
Authentication
Send the token as a bearer credential on every request. There is no separate handshake and no refresh step.
Tokens are hashed on the server and cannot be recovered. Losing one means deleting the API user and issuing a replacement, which immediately revokes the old token.
Scope
A token belongs to one account, and every request names the company it acts on with companyId. Requests are authorised against the API user's per-module permissions, so a token can read invoices without being able to delete them. Last use is recorded on the API user.
Errors
Status
Meaning
400
A required field is missing or fails validation, or companyId was not supplied.
401
No session cookie and no valid bearer token, or the token is not in chronly_api_ form.
403
The API user lacks permission on the module being called, or the account's plan does not include it.
404
The record exists in no company the token can reach, the id is wrong, or companyId is not a company this token owns.
409
The write collides with an existing record - a duplicate externalId or customer email.
500
Unhandled server error. Safe to retry idempotent reads.
Code samples shown as
Invoices
Create, send, schedule, update and delete invoices, record payments against them, and read their email and view activity. Invoice numbering, dates and every money field are computed server-side.
Creates a draft invoice. Send the customer as a buyerId and the line items as name/quantity/price — the invoice number, dates, title, line totals and every money field are derived server-side. The invoice is created in draft; use POST /invoices/{invoiceId}/send to email it.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in.
customer
string · required
The buyerId of an existing customer in this company. Returns 404 if the buyer belongs to another company. The legacy { label, value } object is still accepted.
items
array · required
Line items. Each needs name and price; quantity defaults to 1. Note the field is items, not lineItems.
items[].name
string · required
What the line is for. description is accepted as an alias.
items[].price
number · required
Unit price. rate is accepted as an alias.
items[].quantity
number
Defaults to 1.
items[].taxIds
array of string
Tax rates to charge on this line, by taxId from GET /taxes. Each tax is charged only on the lines that carry it, so a mixed taxable and non-taxable invoice is taxed correctly. Unknown ids return 400.
items[].productId
string
Links the line to a product.
paymentTerms
string
Used to derive paymentDue from invoiceDate. Defaults to the company's invoice default.One ofon-receiptnet-7net-14net-30net-45net-60net-90
paymentDue
string (YYYY-MM-DD)
Overrides paymentTerms. Derived when omitted.
invoiceDate
string (YYYY-MM-DD)
Defaults to today.
invoiceNumber
number
Defaults to the company's highest invoice number plus one.
title
string
Defaults to the company's configured invoice title.
summary
string
Shown under the line items.
invoiceNotes
string
Free-text notes on the invoice.
jobId
string
Attaches the invoice to a job on creation and writes the link to the job activity log.
subTotal / totalTax / totalAmount
number
Computed from items and discounts. Supply all three to override; supplying only some is ignored, so a partly-specified invoice can never disagree with its own lines.
enableReminders
boolean
Defaults to whether the company has any enabled invoice reminder templates.
400companyId is missing, or an items[].taxIds value is not a tax rate in this company.
404The customer buyerId does not exist in this company.
createdBy and createdByUserId come from the authenticated identity and are ignored if sent.
GET/invoices/{invoiceId}Retrieve an invoiceTry it ↗
Returns the invoice together with the buyer record, the company it belongs to, and any purchase records against it. If no invoice matches, the id is retried as a recurring invoice.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
invoiceId
path · required
An invoiceId or a recurringInvoiceId.
companyId
query · required
The company the records belong to.
cURL
curl -X GET \
"https://chronly.ca/api/invoices/{invoiceId}?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Updates a whitelisted set of fields. Anything financial that is derived from payment activity — status, payments, totalPaid, paid, paidAt — is dropped. Sending items without totals recomputes subTotal, totalTax and totalAmount from the new lines, and remainingBalance follows totalAmount.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
invoiceId
path · required
The invoice to update.
companyId
query · required
The company the records belong to.
Request body
customer
string
A buyerId, or the legacy { label, value } object.
items
array
Replaces the line items. Same shape as create, including taxIds. Totals are recomputed unless you send them.
title / summary / invoiceNotes
string
Free-text fields.
invoiceNumber
number
Not re-derived on update — an absent value leaves the existing number alone.
invoiceDate / paymentDue
string (YYYY-MM-DD)
Not re-derived on update.
jobId
string | null
Moving the invoice between jobs updates both jobs and logs the change on each.
attachPdfInvoice
boolean
Attach a rendered PDF when this invoice is emailed.
Filtered list, scoped to single invoices only. Recurring templates are excluded. Paid and overdue are derived states, not just stored status values.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the records belong to.
search
query
Matches customer name or invoice number, case-insensitive.
statuses
query · csv
e.g. sent,viewed,paid. paid also matches settled balances; partially-paid means some payment with a balance left.One ofdraftsentviewedpaidpartially-paidoverduecancelledrefundedscheduledfailed
overdue
query · boolean
true restricts to past-due, unpaid, non-cancelled invoices. Combines with statuses.
startDate / endDate
query (YYYY-MM-DD)
Range on paymentDue, inclusive.
minAmount / maxAmount
query · number
Range on totalAmount, inclusive.
cURL
curl -X GET \
"https://chronly.ca/api/invoices/search?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
POST/invoices/{invoiceId}/sendSend an invoiceTry it ↗
Emails the invoice and moves it from draft to sent, stamping sentAt and sentBy. Sending an already-sent invoice appends to its email history and leaves the status alone, so this doubles as a resend.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
invoiceId
path · required
The invoice to send.
Request body
companyId
string · required
The company the invoice belongs to.
emailAddresses
array of string · required
Recipients. The first is the To address; any others are BCC'd. An empty array sends to nobody and fails at the mail provider.
message
string
Optional note included in the email body.
cURL
curl -X POST \
"https://chronly.ca/api/invoices/{invoiceId}/send" \
-H "Authorization: Bearer chronly_api_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"companyId": "cmp_4k9x2m",
"emailAddresses": ["[email protected]"],
"message": "Invoice for last week's site prep — thanks!"
}'
Response 200
{ "success": true, "message": "Invoice sent successfully" }
Errors
404No invoice with that id in the company.
Whether a PDF is attached is decided by the invoice's attachPdfInvoice flag, falling back to the company setting.
POST/invoices/{invoiceId}/schedule-sendSchedule an invoice to sendTry it ↗
Queues the invoice to be emailed at a future local date and time, and sets its status to scheduled.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
invoiceId
path · required
The invoice to schedule.
Request body
companyId
string · required
The company the invoice belongs to.
sendDate
string (YYYY-MM-DD) · required
Local date to send on.
sendTime
string (HH:mm) · required
Local time to send at.
timezone
string · required
IANA zone the date and time are read in, e.g. America/Vancouver.
emailAddresses
array of string · required
At least one recipient. An empty array is rejected with 400.
POST/invoices/{invoiceId}/update-statusUpdate invoice statusTry it ↗
Sets the status directly, validated against the schema enum. Cancelling an invoice that was converted from a quote releases any deposit applied to it back to a liability.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
invoiceId
path · required
The invoice to update.
Request body
companyId
string · required
The company the invoice belongs to.
status
string · required
The new status.One ofdraftsentviewedpaidoverduecancelledrefundedscheduledfailed
This changes the stored status ONLY. Setting it to sent does NOT email anything — use POST /invoices/{invoiceId}/send for that.
POST/invoices/{invoiceId}/record-paymentRecord a manual paymentTry it ↗
Records a payment taken outside Chronly — cash, cheque, e-transfer — against the invoice, recalculates totalPaid and remainingBalance, and flips the invoice to paid once the balance settles. A matching purchase record is written so the payment appears in the books.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
invoiceId
path · required
The invoice being paid.
Request body
companyId
string · required
The company the invoice belongs to.
paymentType
string · required
How the money arrived.One ofcashchequee-transferbank-transfercredit-card-manualother
amount
number · required
Must be greater than zero. Rounded to two decimals.
paymentDate
string (YYYY-MM-DD) · required
Cannot be in the future.
cardBrand
string
Only meaningful with credit-card-manual.One ofvisamastercardamexdiscover
depositToLedgerAccountId
string
The bank or credit-card ledger account the money landed in. Omit for cash and cheques, which stay in Undeposited Funds until a deposit banks them.
400paymentType, amount or paymentDate is missing, the amount is not positive, the date is in the future, or depositToLedgerAccountId is not a live bank or credit-card account in this company.
404No invoice with that id in the company.
GET/invoices/{invoiceId}/email-eventsList email eventsTry it ↗
Every delivery event the mail provider reported for this invoice — processed, delivered, open, click, bounce, dropped — newest first.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
invoiceId
path · required
The invoice.
companyId
query · required
The company the invoice belongs to.
cURL
curl -X GET \
"https://chronly.ca/api/invoices/{invoiceId}/email-events?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
GET/invoices/{invoiceId}/view-statsInvoice view statsTry it ↗
How often the customer-facing invoice page was viewed, from page-view tracking. Portal views are excluded, so this reflects the customer rather than your own team.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
invoiceId
path · required
The invoice.
companyId
query · required
The company the invoice belongs to.
cURL
curl -X GET \
"https://chronly.ca/api/invoices/{invoiceId}/view-stats?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
POST/invoices/recurringCreate a recurring invoiceTry it ↗
Creates a recurring invoice template. Templates live in their own collection keyed by recurringInvoiceId, and are returned by GET /invoices alongside ordinary invoices. Creating the template does not start it — set its schedule with PUT /invoices/{recurringInvoiceId}/recurring/schedule.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the template in.
customer
string · required
The buyerId of an existing customer. The legacy { label, value } object is still accepted.
items
array · required
Line items copied onto each generated invoice. Same shape as POST /invoices, including taxIds.
title
string
Defaults to the company's configured invoice title.
subTotal / totalTax / totalAmount
number
Computed from items. Supply all three to override.
paymentSettings
object
Overrides the company payment defaults, which are used when omitted.
400A required field is missing, the recurrence-specific field for the chosen recurrenceType is absent, the timezone or sendTime is invalid, an email address is malformed, or endDate is not after startDate.
403The account does not have the recurringInvoices capability.
404No recurring invoice with that id in the company.
PATCH/invoices/{invoiceId}/recurring/schedulePause or resume a scheduleTry it ↗
Stops or restarts generation without discarding the schedule.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
404No recurring invoice with that id in the company.
GET/taxes/company/{companyId}List tax ratesTry it ↗
The company's tax registry. Use the taxId values here for items[].taxIds when creating an invoice — without this endpoint that parameter is unusable, since the ids appear nowhere else. Soft-deleted rates are omitted. The response is a bare array, not an object.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
path · required
The company whose tax rates to list. There is no query parameter on this endpoint — the company goes in the path.
cURL
curl -X GET \
"https://chronly.ca/api/taxes/company/{companyId}" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Returns all checkouts for the company, newest first, including drafts and duplicates. Ad-hoc checkouts with a maxPurchases cap also carry live purchase counts.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the records belong to. Required when authenticating with a token, since a token is not bound to one company.
cURL
curl -X GET \
"https://chronly.ca/api/checkout?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Creates a checkout page. Payment methods fall back to the company defaults when omitted, and a checkout with an expiry and an expiry webhook schedules that webhook at creation time.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in. Required for token auth.
name
string · required
Internal name for the checkout.
type
string · required
Checkout type. Accepts a raw string or { value }.One ofphysicaldigitalserviceprofessional_servicetaxable_servicetax_exemptbundle
price
number · required
Unit price, parsed as a float.
checkoutMode
string
Defaults to ad-hoc. Ad-hoc checkouts also require description.One ofad-hocproduct
currency
string
Defaults to CAN.
billingType
string
Defaults to one-time; set recurring with a recurrence object.One ofone-timerecurring
externalId
string
Your own identifier. Unique per account — a repeat returns 409.
maxPurchases
number
Cap on completed and pending purchases.
checkoutExpires
boolean
When true, expiresSettings.expiresAt must be a valid datetime.
metadata
object
Free-form key/value data stored on the checkout and echoed in webhooks.
Processes a payment against a checkout: card, Apple Pay, Google Pay or bank, with surcharge and tax handling, authorization-only capture, and post-purchase webhooks.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in. Required for token auth.
checkoutId
string · required
The checkout being paid.
quantity
number
Units purchased, when the checkout allows quantity selection.
This is the endpoint the hosted checkout page itself calls. Talk to us before driving it directly — surcharge, tax and receipt behaviour depend on company payment settings.
Quotes
Quotes carry line items, deposits and approval state, and convert into invoices or jobs. Send the customer as a buyerId and the lines as description/quantity/price — line amounts, totals and tax are recalculated from the items every time a quote is saved, so values you send for them are overwritten.
Creates a draft quote. Line amounts, subtotal, totalTax and totalAmount are computed from the items on save — values you send for them are overwritten, so there is no way for a quote to disagree with its own lines.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in.
customer
string · required
The buyerId of an existing customer in this company. Returns 404 if the buyer belongs to another company. The legacy { label, value } object is still accepted. Name, email, phone and address are copied onto the quote from the buyer record.
quoteNumber
string · required
Your reference for the quote. NOT generated server-side — unlike an invoice number, you must supply one. The portal uses the form QUO-481920.
items
array · required
Line items.
items[].description
string · required
What the line is for. name is accepted as an alias, so an invoice-shaped line works here too.
items[].price
number · required
Unit price. rate is accepted as an alias. Defaults to 0, so omitting it silently creates a free line.
items[].quantity
number
Defaults to 1.
items[].taxIds
array of string
Tax rates to charge on this line, by taxId from GET /taxes. Each tax is charged only on the lines that carry it. Unknown ids return 400.
items[].taxRates
array
The long form of taxIds, as { id, name, value } where value is the percentage. Takes precedence when both are sent.
title
string
Defaults to the company's configured quote title.
quoteDate
string (YYYY-MM-DD)
Defaults to today.
validUntil
string (YYYY-MM-DD)
Defaults to the company's quote validity period. Past this date the quote auto-expires and can no longer be accepted.
summary / quoteNotes
string
Free-text fields shown on the quote.
deposit
object
Deposit required before work starts, as { type, value } where type is percent or dollarAmount.One ofpercentdollarAmount
allowLinkAcceptance
boolean
Lets the customer accept from the emailed link without signing in. Required for the public acceptance flow.
attachPdfQuote
boolean
Attach a rendered PDF when the quote is emailed.
jobId
string
Attaches the quote to a job on creation and writes the link to the job activity log.
400The quote is not in sent or viewed status, or it has expired.
404No quote with that id in the company.
Sending acceptanceType: "client" takes a different, PUBLIC code path used by the emailed acceptance link — it needs no token, requires the quote to have allowLinkAcceptance, and records the accepter's IP and user agent. Do not use it for server-to-server acceptance.
POST/quotes/{quoteId}/convert-to-invoiceConvert a quote to an invoiceTry it ↗
Creates an invoice from the quote, copying its line items and totals exactly so the two always agree. A paid deposit on the quote is carried across as a payment on the invoice, and the quote moves to converted.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
quoteId
path · required
The quote to convert.
Request body
companyId
string · required
The company the quote belongs to.
paymentTerms
string
Sets the new invoice's paymentDue relative to today. Defaults to net-30.One ofon-receiptnet-7net-14net-30net-45net-60net-90
Jobs are the container the rest of the work hangs off: scheduled visits, assigned crew, linked quotes and invoices, documents, client shares and an activity log.
Returns jobs newest first, each with a scheduling summary so a list view needs no follow-up calls. Visibility is the greater of the company-wide jobs permission and per-job assignment, so a token scoped below viewer sees only jobs it created or is assigned to.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the records belong to. Required when authenticating with a token, since a token is not bound to one company.
cURL
curl -X GET \
"https://chronly.ca/api/jobs?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Creates a job. Assigning crew at creation writes viewer-level assignment records, emails those people, and logs both the creation and the assignment on the job.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in. Required for token auth.
name
string · required
Job name. Blank or whitespace is rejected.
status
string
Defaults to unscheduled.One ofdraftscheduledunscheduledcompletedcancelledarchivedactiveon-hold
Chronological log of everything that happened on the job: creation, assignments, linked and unlinked quotes and invoices.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
jobId
path · required
The job id.
companyId
query · required
The company the records belong to. Required when authenticating with a token, since a token is not bound to one company.
cURL
curl -X GET \
"https://chronly.ca/api/jobs/{jobId}/activity?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Response 200
{ "success": true, "activity": [] }
GET/jobs/{jobId}/assignmentsList crew assignmentsTry it ↗
The people on the job and the role each holds. POST the same path to add one; PUT and DELETE /jobs/{jobId}/assignments/{assignmentId} change or remove it.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
jobId
path · required
The job id.
companyId
query · required
The company the records belong to. Required when authenticating with a token, since a token is not bound to one company.
cURL
curl -X GET \
"https://chronly.ca/api/jobs/{jobId}/assignments?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Share links that let a client view the job. POST creates one; PUT and DELETE /jobs/{jobId}/shares/{shareId} manage it; /jobs/{jobId}/share-email-stats reports on the share emails.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
jobId
path · required
The job id.
companyId
query · required
The company the records belong to. Required when authenticating with a token, since a token is not bound to one company.
cURL
curl -X GET \
"https://chronly.ca/api/jobs/{jobId}/shares?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Bearer chronly_api_… - the token issued to the API user.
jobId
path · required
The job id.
companyId
query · required
The company the records belong to. Required when authenticating with a token, since a token is not bound to one company.
cURL
curl -X GET \
"https://chronly.ca/api/jobs/{jobId}/documents?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Response 200
{ "success": true, "documents": [] }
POST/jobs/{jobId}/assignmentsAssign a user to a jobTry it ↗
Adds a crew member to the job, or updates their role if they are already on it. A genuinely new assignment also emails the person; a role change on an existing assignment does not.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
jobId
path · required
The job.
companyId
query · required
IN THE QUERY STRING. Job sub-routes authorize before reading the body, so a companyId in the body is not seen.
Request body
userId
string · required
The user to assign.
role
string
Their role on this job. Anything else falls back to viewer.One ofviewereditoradmin
permissions.canCompleteJob
boolean
Lets a viewer mark the job complete. Editors and admins can already do this through their role, so it is only stored for viewers.
POST/jobs/{jobId}/restoreRestore a cancelled jobTry it ↗
Brings a cancelled job back. Its new status comes from its schedule — scheduled if it has visits, unscheduled if not — rather than from whatever it was before the cancellation.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
jobId
path · required
The job to restore.
companyId
query · required
IN THE QUERY STRING, not the body.
Request body
restoreVisits
boolean
Also restore the visits that were cancelled with the job. Defaults to false.
POST/jobs/{jobId}/sharesCreate a client share linkTry it ↗
Creates a share link that lets a customer view the job without an account, and optionally emails it to them. The share is reached at /{company}/job/{shareCode}.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
jobId
path · required
The job to share.
companyId
query · required
IN THE QUERY STRING, not the body.
Request body
recipientEmail
string · required
Who the link is for.
recipientName
string
Display name for the recipient.
canUploadDocuments
boolean
Lets the recipient upload files to the job. Defaults to false.
expiresAt
string (ISO 8601)
When the link stops working. Never expires when omitted.
Customers — buyers, in the data model — are the records quotes, invoices and checkouts point at. Creation guards against duplicate email addresses within a company.
Creates a customer with a generated buyerId. If the email already exists in the company the call returns 409 with the existing record instead of creating a second one — send forceCreate to override.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in. Required for token auth.
emailAddress
string · required
Lower-cased and checked against existing customers in the company.
Creates a product with a generated productId. Any inventory stock changes sent along are stamped with the creating identity and a timestamp.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in.
name
string · required
Product name.
type
string · required
What kind of thing this is. Drives tax treatment.One ofphysicaldigitalserviceprofessional_servicetaxable_servicetax_exempt
billingType
string · required
Whether it is billed once or on a recurrence.One ofone-timerecurring
pricing
object · required
Price and currency, as { price, currency }. NOTE: there is no top-level price field — a bare price is silently discarded and the request fails validation.
pricing.price
number · required
Unit price. Must be zero or greater.
pricing.currency
string
Defaults to CAD.
recurrence
string
Required in practice when billingType is recurring.One ofdailyweeklymonthlyyearly
status
string
Defaults to active.One ofactiveinternal-onlyinactivearchived
sku
string
Your own stock-keeping code.
description
string
Customer-facing description.
taxable
boolean
Whether tax applies to this product.
inventory
object
Tracking settings and opening stock changes. Use POST /products/{productId}/enable-inventory to turn tracking on afterwards.
Streams a stored product PDF through Chronly rather than handing back a storage URL, for viewers that will not follow a redirect. Same company-prefix restriction as the presigned URL.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the file belongs to.
s3Key
query · required
Storage key, as returned by POST /products/upload.
cURL
curl -X GET \
"https://chronly.ca/api/products/pdf-proxy?companyId=cmp_4k9x2m&s3Key=%3Cs3Key%3E" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Response 200
The PDF bytes, with Content-Type: application/pdf.
Errors
400s3Key is missing.
403The key is not under this company's prefix.
Discounts
Discount codes applied at checkout, with usage limits, expiry and validation. Stats can come back with the list.
Creates a discount code. Codes are compared upper-cased and must be unique within the company.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in.
name
string · required
Internal name for the discount.
code
string · required
The code customers type. Must be unique in the company, compared case-insensitively.
discountType
string · required
Whether value is a percentage or a dollar amount. Send the bare string; the { label, value } object the portal uses is also accepted.One ofpercentagedollar
value
number · required
The amount off. Must be 0-100 for percentage, and positive for dollar. NOTE: this is the magnitude — there is no amount field.
applicableTypes
object · required
What the code may be used on, as { checkouts, invoices, products, all } booleans. At least one must be set; an empty object is rejected.
description
string
Customer-facing description.
maxUses
number
Total redemptions allowed. null means unlimited.
startDate / endDate
string (ISO 8601)
Window the code is valid in. startDate must be before endDate.
minimumAmount
number
Order subtotal the code needs before it applies. Defaults to 0.
maximumDiscountAmount
number
Caps a percentage discount. null means no cap.
status
string
Defaults to active.One ofactiveinactiveexpiredused_up
500The code already exists in this company, a percentage value is outside 0-100, a dollar value is negative, startDate is not before endDate, or applicableTypes is empty. These validations pre-date the API and throw without a status, so they surface as 500 rather than 400 — the message body still names the problem.
createdBy is taken from the authenticated identity and ignored if sent.
GET/discounts/{discountId}Retrieve a discountTry it ↗
Returns one discount with its usage counters.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the records belong to. Find it in the portal under Settings > API.
discountId
path · required
The discount id.
cURL
curl -X GET \
"https://chronly.ca/api/discounts/{discountId}?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
Response 200
{ "discountId": "dsc_2k9m", "usedCount": 23 }
PUT/discounts/{discountId}Update a discountTry it ↗
Changes amount, limits, expiry or active state.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
discountId
path · required
The discount to update.
Request body
companyId
string · required
The company the record belongs to. Find it in the portal under Settings > API.
Checks a code against a cart before charging: active, in date, within its usage limit and applicable. /discounts/validate-public is the unauthenticated variant the hosted checkout uses.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
Request body
companyId
string · required
The company to create the record in. Required for token auth.
Calendars and the scheduled items on them. A scheduled item can stand alone or be linked to a job, in which case it appears as a visit on that job. Availability endpoints answer "who is free between these two times".
Returns the calendars the caller can see. Visibility is per calendar (private, internal or public) and is combined with the scheduling manage_all permission.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the records belong to. Find it in the portal under Settings > API.
cURL
curl -X GET \
"https://chronly.ca/api/scheduling/calendars?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
400userIds is not an array, or start or end is missing.
GET/scheduling/assignable-usersList assignable usersTry it ↗
Returns the users who can be assigned to a booking. API users and users who never accepted their invite are excluded, as are users hidden from scheduling.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the records belong to. Find it in the portal under Settings > API.
cURL
curl -X GET \
"https://chronly.ca/api/scheduling/assignable-users?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
GET/scheduling/availabilityGet an availability scheduleTry it ↗
Returns the working hours for one owner — a user or the company — including date overrides and extended leave. Requesting someone else's schedule needs the manage_all scheduling permission.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the schedule belongs to.
ownerType
query
Whose schedule to read. Defaults to user.One ofusercompany
ownerId
query
The user whose schedule to read. Defaults to the caller. Ignored when ownerType is company.
list
query
Pass all to return every schedule in the company instead of one. Requires manage_all.
cURL
curl -X GET \
"https://chronly.ca/api/scheduling/availability?companyId=cmp_4k9x2m" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
403Reading another user's or the company's schedule without manage_all.
PUT/scheduling/availabilitySet an availability scheduleTry it ↗
Creates or replaces the schedule for one owner. This is a REPLACE — weeklyHours, overrides and leave are each overwritten wholesale, and an omitted array is stored as empty.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
companyId
query · required
IN THE QUERY STRING. Scheduling writes authorize before the body is read, so a companyId in the body is not seen.
Request body
ownerType
string
Whose schedule to write. Defaults to user.One ofusercompany
ownerId
string
The user to write. Defaults to the caller. Ignored when ownerType is company.
timezone
string
IANA zone the hours are expressed in.
weeklyHours
array
Recurring hours, as [{ weekday, intervals: [{ start, end }] }]. weekday is 0-6 with 0 = Sunday; start and end are HH:MM, 24-hour.
overrides
array
Date-specific exceptions, as [{ date: "YYYY-MM-DD", available, intervals }]. available: false with no intervals is a day off.
leave
array
Continuous stretches away, as [{ startDate, endDate }]. A null endDate is open-ended.
403Editing your own schedule without manage_availability, or anyone else's without manage_all.
GET/scheduling/availability/working-intervalsResolve working intervalsTry it ↗
Flattens weekly hours, overrides and leave into the concrete intervals each user actually works over a date range — what you want before deciding when to book.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the users belong to.
from
query (YYYY-MM-DD) · required
Start of the range, inclusive.
to
query (YYYY-MM-DD) · required
End of the range, inclusive.
userIds
query · csv · required
Users to resolve. An empty list returns an empty object rather than every user.
cURL
curl -X GET \
"https://chronly.ca/api/scheduling/availability/working-intervals?companyId=cmp_4k9x2m&from=%3Cfrom%3E&to=%3Cto%3E&userIds=%3CuserIds%3E" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
PATCH/scheduling/assignable-usersHide or show a user in schedulingTry it ↗
Controls whether a user appears in the assignable-users list. Hiding does not affect their existing bookings. Requires the manage_all scheduling permission.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
companyId
query · required
IN THE QUERY STRING, not the body.
Request body
userId
string · required
The user to hide or show.
hidden
boolean · required
Must be a real boolean — a missing or non-boolean value is rejected with 400.
GET/scheduling/calendar-feedRead the calendar feedTry it ↗
Everything on the calendar between two dates, already merged and flattened for display — scheduled items plus, optionally, jobs. Callers without manage_all see only the calendars they have access to.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
companyId
query · required
The company the calendars belong to.
from
query (ISO 8601) · required
Start of the window.
to
query (ISO 8601) · required
End of the window.
calendarIds
query · csv
Restrict to these calendars.
assigneeIds
query · csv
Restrict to items assigned to these users.
includeJobs
query · boolean
Pass true to include jobs alongside scheduled items.
cURL
curl -X GET \
"https://chronly.ca/api/scheduling/calendar-feed?companyId=cmp_4k9x2m&from=%3Cfrom%3E&to=%3Cto%3E" \
-H "Authorization: Bearer chronly_api_xxxxxxxx"
PUT/scheduling/items/{itemId}/visit-updateLog a visit wrap-upTry it ↗
Records how a job visit went. Deliberately separate from the item PUT: moving a visit needs editor rights on the job, but the person who actually did the work may only be a viewer on the crew, so this route authorizes on assignment instead and does NOT require the view_calendar permission.
Parameters
Authorization
header · required
Bearer chronly_api_… - the token issued to the API user.
Content-Type
header · required
application/json
itemId
path · required
The scheduled item. Must be a job visit.
companyId
query · required
IN THE QUERY STRING, not the body.
Request body
outcome
string
How the visit went. Anything unrecognised falls back to completed.One ofcompletedpartialblocked
internalNotes
string
Notes for your team only. Truncated at 5000 characters.
publicNotes
string
Notes the customer can see on a job share. Truncated at 5000 characters.
followUpRequired
boolean
Flags that more work is needed.
followUpNotes
string
Only stored when followUpRequired is true. Truncated at 1000 characters.
jobComplete
boolean
Also marks the parent job complete. The caller must be allowed to complete the job.