For pros and the tools they use
NorTech Pro API and webhooks
Connect the software you already use to your NorTech Pro Business account. Read and write your own customers, estimates, jobs and invoices, get a signed message the moment something happens, download QuickBooks files, and subscribe to your calendar.
Get started
API access is part of the Business plan and above. Create a key in the pro app under Developers, choose what it may do, and copy it. It is shown once. Every request goes to https://nortech.site/api/v1/pro and returns JSON.
The full machine-readable description is the OpenAPI 3 document at /api/v1/openapi.json. Import it into Postman, Insomnia or a code generator. Using Zapier or Make? Start with the Zapier and Make guide.
Amounts are whole cents. Times are UTC in ISO 8601. The API only ever shows records that belong to the pro who owns the key: your own customers, not marketplace customers.
Authentication
Send the key as a bearer token on every request. A key looks like ntk_ab12cd34_ followed by 40 characters. NorTech keeps only a hash of it, so a lost key cannot be shown again: make a new one and revoke the old.
A key can be limited to a list of IP addresses or ranges, revoked at any time, or rotated: rotating creates a new key with the same settings and the old key keeps working for 24 hours so nothing breaks while you switch over. GET /me returns the key's name and scopes and is the connection test to use in other tools.
Scopes
Each key carries the scopes you pick, one read and one write scope per resource. Write does not include read. A call outside the key's scopes answers 403 insufficient_scope.
| Resource | Scopes | What you can do |
|---|---|---|
| Customers | customers:read, customers:write | list, get, create, update, delete |
| Properties | properties:read, properties:write | list, get, create, update, delete |
| Estimates | estimates:read, estimates:write | list, get, create, update |
| Jobs | jobs:read, jobs:write | list, get, create, update |
| Invoices | invoices:read, invoices:write | list, get, create, update |
| Payments | payments:read | list, get |
| Price book | price_book:read, price_book:write | list, get, create, update, delete |
| Calendar events | calendar_events:read | list |
| Webhooks | webhooks:read, webhooks:write | list, get, create, delete |
Errors
Every error has the same shape and an HTTP status that matches its type. Quote the request_id if you contact support.
| Status | Type | Meaning |
|---|---|---|
| 400 | invalid_request | The request is malformed or a field is wrong. See issues for each field. |
| 401 | authentication | The key is missing, wrong, revoked, expired, or used from an address that is not allowed. |
| 402 | plan_required | The plan no longer includes the API. Existing data stays; upgrade to use the API again. |
| 403 | permission | The key does not carry the scope this call needs. |
| 404 | not_found | No such record for your account. |
| 409 | conflict | The record cannot be changed in its current state, or an Idempotency-Key was reused with a different request. |
| 429 | rate_limit | Too many requests. Wait for the Retry-After seconds. |
| 500 | server | Something went wrong on our side. |
Pagination
List calls return the newest first. Pass limit (1 to 100, default 25) and, for the next page, cursor set to next_cursor from the previous answer. When has_more is false you have everything. Cursors are opaque and stay valid while records are added.
Most lists also take updated_since (an ISO 8601 time), which is the cheap way to poll for changes.
Idempotency
Add an Idempotency-Key header (8 to 200 characters) to any POST or PATCH you may need to retry. The first answer is stored for 24 hours and repeated for the same key and the same request, with Idempotent-Replayed: true. The same key with a different request answers 409 idempotency_key_reused. Server errors are not stored, so a retry runs again.
Rate limits
A key may make 120 requests a minute by default; its owner can set it between 10 and 600. Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the minute resets). When you are over, the answer is 429 with Retry-After.
Resources
Paths are relative to /api/v1/pro. Lists are GET /{path}, one record is GET /{path}/{id}, create is POST /{path}, change is PATCH /{path}/{id} and remove is DELETE /{path}/{id}, where the resource allows it. Fields marked read only are set by NorTech. Estimates show the option the customer chose, else the recommended one, else the first.
Customers /customers
The people and companies you work for. These are your own customers, kept apart from marketplace customers.
Operations: GETGET /{id}POSTPATCH /{id}DELETE /{id}
| Field | Type | Notes |
|---|---|---|
id | string | The customer id. Read only. |
name | string | Full name or company name. Required on create. |
email | string | Email address. Optional. |
phone | string | Phone number. Optional. |
company | string | Company name, if any. Optional. |
notes | string | Private notes. Never shown to the customer. Optional. |
tags | string array | Labels you use to group customers. Optional. |
created_at | datetime | When it was created (UTC). Read only. |
updated_at | datetime | When it last changed (UTC). Read only. |
Filters: email, updated_since.
Properties /properties
The addresses where you do the work. Each belongs to one customer.
Operations: GETGET /{id}POSTPATCH /{id}DELETE /{id}
| Field | Type | Notes |
|---|---|---|
id | string | The property id. Read only. |
customer_id | string | The customer this address belongs to. Required on create. |
label | string | A short name such as "Main house". Optional. |
street | string | Street address. Required on create. |
city | string | City. Required on create. |
state | string | Two-letter state code. Required on create. |
zip | string | ZIP code. Required on create. |
notes | string | Access notes, pets, parking. Optional. |
created_at | datetime | When it was created (UTC). Read only. |
Filters: customer_id.
Estimates /estimates
Written quotes you send to your customers. Totals are computed by NorTech from the line items.
Operations: GETGET /{id}POSTPATCH /{id}
| Field | Type | Notes |
|---|---|---|
id | string | The estimate id. Read only. |
number | string | Your estimate number. Read only. |
customer_id | string | The customer. Required on create. |
property_id | string | Where the work happens. Optional. |
title | string | A short title for the work. Required on create. |
status | string | DRAFT, SENT, VIEWED, ACCEPTED, DECLINED or EXPIRED. Only DRAFT estimates can be edited through the API. Sending and acceptance happen in the app or by the customer. Read only. One of DRAFT, SENT, VIEWED, ACCEPTED, DECLINED, EXPIRED. |
line_items | line items | The lines of the estimate. Required on create. |
subtotal_cents | integer | Sum of the lines, in cents. Read only. |
tax_cents | integer | Sales tax, in cents. Read only. |
total_cents | integer | Total in cents. Read only. |
valid_until | date | Last day the estimate can be accepted. Optional. |
accepted_at | datetime | When the customer accepted. Read only. |
created_at | datetime | When it was created (UTC). Read only. |
updated_at | datetime | When it last changed (UTC). Read only. |
Filters: status, customer_id, updated_since.
Jobs /jobs
Scheduled and finished work for your own customers. Marketplace jobs appear in calendar events, not here.
Operations: GETGET /{id}POSTPATCH /{id}
| Field | Type | Notes |
|---|---|---|
id | string | The job id. Read only. |
customer_id | string | The customer. Required on create. |
property_id | string | Where the work happens. Optional. |
estimate_id | string | The estimate this job came from. Optional. |
title | string | A short title. Required on create. |
status | string | UNSCHEDULED, SCHEDULED, IN_PROGRESS, COMPLETED or CANCELLED. Optional. One of UNSCHEDULED, SCHEDULED, IN_PROGRESS, COMPLETED, CANCELLED. |
scheduled_start | datetime | Planned start (UTC). Optional. |
scheduled_end | datetime | Planned end (UTC). Optional. |
completed_at | datetime | When the work was finished. Read only. |
notes | string | Private notes. Optional. |
created_at | datetime | When it was created (UTC). Read only. |
updated_at | datetime | When it last changed (UTC). Read only. |
Filters: status, customer_id, updated_since.
Invoices /invoices
Bills you send to your customers. Payments are recorded by NorTech when the customer pays; read them from the payments resource.
Operations: GETGET /{id}POSTPATCH /{id}
| Field | Type | Notes |
|---|---|---|
id | string | The invoice id. Read only. |
number | string | Your invoice number. Read only. |
customer_id | string | The customer. Required on create. |
job_id | string | The job being billed. Optional. |
title | string | A short title shown on the invoice. Optional. |
status | string | DRAFT, SENT, PARTIAL, PAID, OVERDUE (sent or partly paid and past due) or VOID. Only DRAFT invoices can be edited through the API. Read only. One of DRAFT, SENT, PARTIAL, PAID, OVERDUE, VOID. |
line_items | line items | The lines of the invoice. Required on create. |
subtotal_cents | integer | Sum of the lines, in cents. Read only. |
tax_cents | integer | Sales tax, in cents. Read only. |
total_cents | integer | Total in cents. Read only. |
amount_paid_cents | integer | Paid so far, in cents. Read only. |
due_date | date | Payment due date. Optional. |
issued_at | datetime | When the invoice was sent. Read only. |
paid_at | datetime | When it was fully paid. Read only. |
created_at | datetime | When it was created (UTC). Read only. |
updated_at | datetime | When it last changed (UTC). Read only. |
Filters: status, customer_id, updated_since.
Payments /payments
Money received against your invoices. Read only.
Operations: GETGET /{id}
| Field | Type | Notes |
|---|---|---|
id | string | The payment id. Read only. |
invoice_id | string | The invoice that was paid. Read only. |
amount_cents | integer | Amount in cents. Read only. |
method | string | CARD, BANK, CASH, CHECK or OTHER. Read only. One of CARD, BANK, CASH, CHECK, OTHER. |
refunded_cents | integer | Part of this payment that was refunded, in cents. Read only. |
reference | string | The note the pro left on the payment, such as a check number. Read only. |
received_at | datetime | When the money was received. Read only. |
created_at | datetime | When it was created (UTC). Read only. |
Filters: invoice_id, received_since.
Price book /price-book
The services and materials you quote and bill. The same list feeds your estimates and invoices.
Operations: GETGET /{id}POSTPATCH /{id}DELETE /{id}
| Field | Type | Notes |
|---|---|---|
id | string | The price book item id. Read only. |
name | string | Item name. Required on create. |
description | string | Longer description shown on estimates. Optional. |
unit | string | Unit of measure: each, hour, sqft, ft. Optional. |
unit_price_cents | integer | Price per unit in cents. Required on create. |
taxable | boolean | Whether sales tax applies. Optional. |
active | boolean | Inactive items are hidden when building an estimate. Optional. |
created_at | datetime | When it was created (UTC). Read only. |
Filters: active.
Calendar events /calendar-events
Everything on your calendar: jobs for your own customers and your marketplace jobs. Read only.
Operations: GET
| Field | Type | Notes |
|---|---|---|
id | string | Event id. Read only. |
kind | string | OWN_JOB for your own customers, MARKETPLACE_JOB for a NorTech booking. Read only. One of OWN_JOB, MARKETPLACE_JOB. |
title | string | What the event is. Read only. |
start | datetime | Start (UTC). Read only. |
end | datetime | End (UTC). Read only. |
location | string | Address, when you may see it. Read only. |
job_id | string | The job record behind the event. Read only. |
Filters: from, to.
Webhooks /webhooks
Subscribe a URL to events from code. This is what Zapier and Make use to start a workflow the moment something happens. Deliveries are signed.
Operations: GETGET /{id}POSTDELETE /{id}
| Field | Type | Notes |
|---|---|---|
id | string | The webhook id. Read only. |
url | string | A public https URL. Private, local and reserved addresses are refused. Required on create. |
events | string array | Event types to send, such as invoice.paid. Required on create. |
description | string | A note to yourself. Optional. |
active | boolean | False once the endpoint was switched off. Read only. |
secret | string | Signing secret. Returned once, when the webhook is created. Read only. |
created_at | datetime | When it was created (UTC). Read only. |
Webhooks
Add a webhook URL in the Developers page (or create one with POST /webhooks) and pick the events it should receive. NorTech sends an HTTPS POST with a JSON body and a signature. Answer with any 2xx status within 10 seconds to confirm. The URL must be a public HTTPS address; private, local and cloud-metadata addresses are refused, and redirects are not followed.
| Event | Sent when |
|---|---|
customer.created | A customer was added |
estimate.accepted | A customer accepted an estimate |
job.scheduled | A job was scheduled |
job.completed | A job was completed |
invoice.created | An invoice was created |
invoice.paid | An invoice was paid |
invoice.overdue | An invoice became overdue |
review.received | A customer left a review |
Headers sent with every delivery: NorTech-Signature, NorTech-Event, NorTech-Event-Id (the same id on every retry, so you can ignore repeats), NorTech-Delivery and NorTech-Attempt.
If your URL does not answer 2xx, NorTech tries again after 1 min, 5 min, 30 min, 2 h, 6 h, 24 h (7 attempts in all). The delivery log in the Developers page shows every attempt with the answer we got, and you can send any delivery again by hand. If 5 deliveries in a row use up all their attempts, the webhook is switched off and you are told; fix the URL and switch it back on.
Verify signatures
Each webhook has a signing secret that starts with whsec_, shown once when you create it (you can reset it any time). The NorTech-Signature header looks like t=1760455802,v1=9f2c.... Compute HMAC-SHA256 of {t}.{raw body} with the secret, compare it to v1 in constant time, and reject anything with a timestamp more than 5 minutes old, which stops a captured request from being replayed.
Calendar link
In the Developers page you get a private calendar address that works in Google Calendar, Apple Calendar and Outlook. It lists your marketplace jobs and the jobs you scheduled for your own customers, and updates on its own. Anyone who has the address can see the calendar, so you can replace it at any time and the old one stops working. The same events are available from GET /calendar-events.
Two-way Google Calendar sync
QuickBooks files
On the Business plan and above, the Developers page downloads your books in the layouts QuickBooks imports: invoices, payments, customers and items as CSV, one IIF file for QuickBooks Desktop, a general ledger style journal where every entry balances, and a sales tax report by month (accrual or cash basis). You choose the account names (income, sales tax payable, accounts receivable, deposit) once and every file uses them. Text that begins with a formula character is neutralized so a spreadsheet never runs it.
Live QuickBooks connection
Security
Keys are stored as SHA-256 hashes and compared in constant time. Every key creation, rotation, revocation and webhook change is written to an audit trail you can see in the Developers page, with the address it came from. Signing secrets are encrypted at rest. Secrets never appear in logs. Rate limits are counted in the database, so they hold across servers. Report a security problem through the contact page.
