API
Connect Inspec to your accounting software, automations and other tools with the Inspec API.
Overview
The Inspec API lets other software read your company's projects, suppliers, products, invoices and payments, and add suppliers. Common uses include syncing invoices and payments into your accounting software, building automations with tools like Zapier, and pulling your product library into other design tools.
Need the API to do more?
Email [email protected] and tell us what you want to connect. We add endpoints and fields when studios ask for them, and we're glad to hear what would help.
Keys are either read only or read and write. A read-only key can't change anything in your account. A read and write key can also create suppliers. The API doesn't include the contents of your schedules.
All requests go to:
https://api.inspec.design/v1Authentication
Creating an API key
Only admins can create API keys.
- Open API settings - Go to Settings and select API.
- Create a key - Click Create key and give it a name that describes where it will be used, for example "Zapier" or "Xero sync".
- Choose its access - Choose Read only for tools that only pull data out of Inspec. Choose Read and write only if the tool needs to add data to Inspec. You can't change this later, so create a new key if you need different access.
- Copy the key - Copy the key and store it somewhere safe. For security, it's only shown once.
You can revoke a key from the same page at any time, and anything using it will stop working immediately.
Keep your keys secret
Anyone with one of your keys can read your company's data, and a read and write key can also add to it. Don't share keys by email or include them in website code. If a key is exposed, revoke it and create a new one.
Making requests
Send your key in the Authorization header of every request:
curl https://api.inspec.design/v1/me \
-H "Authorization: Bearer insp_your_key_here"The API is available to companies with an active subscription or trial.
Each key can make up to 120 requests a minute, shared across all endpoints. If you go over, you'll get a 429 with a Retry-After header.
Conventions
Pagination
List endpoints return a page of results and a cursor for the next page:
{
"data": [],
"nextCursor": "cm7x2k9lq0001abcd"
}| Parameter | Description |
|---|---|
limit | Results per page, from 1 to 100. Defaults to 50. |
cursor | The nextCursor from the previous page. |
updatedSince | Only return records updated at or after this time, e.g. 2026-03-01T09:00:00Z. A date on its own, like 2026-03-01, means midnight UTC. Write + in a timezone offset as %2B. |
Unknown parameters return a 400, so a typo can't silently change what you get back.
Results are ordered newest first. To get the next page, repeat the request with cursor set to nextCursor. When nextCursor is null, you've reached the last page. If the record a cursor points to is deleted while you're paging, you'll get a 400 invalid_request. Start again from the first page.
Syncing changes
To keep another system up to date, record the time you start each sync and pass it as updatedSince next time. You'll get the records that were created or changed in between.
updatedSince only returns records that are still in the list, so it won't tell you when something leaves it. For example, a deleted product or supplier, a deleted project along with its invoices and payments, or an invoice reopened as a draft along with its payments. To catch these, run a full sync without updatedSince from time to time and remove anything that's no longer returned.
Money
Amounts are whole numbers in the smallest unit of the currency (cents or pence), alongside a lowercase currency code. For example, "total": 125050 with "currency": "aud" is $1,250.50 AUD. Product costs and RRPs are always before tax.
Dates
- Timestamps such as
createdAtandupdatedAtare in UTC, e.g.2026-03-14T09:30:00.000Z. - Document dates such as an invoice date or payment date are written as
YYYY-MM-DDin your company's timezone, so they match the dates on your PDFs.
Creating records
Creating a record needs a read and write key. Send the record as a JSON object with a Content-Type: application/json header. Fields you leave out are left empty, and unknown fields return a 400, the same as unknown parameters.
A successful request returns 201 with the new record, in the same shape you'd get from fetching it.
Some records must be unique, for example supplier names. Creating a duplicate returns a 409 conflict with the existing record's id in details.id. This makes it safe to retry a request that timed out: if the first attempt went through, the retry tells you where to find it.
Errors
Errors return a matching HTTP status and a body like this:
{
"error": {
"code": "not_found",
"message": "Invoice not found"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter or field is invalid or unknown. details lists each problem. |
| 401 | unauthorized | The key is missing, invalid or revoked. |
| 403 | subscription_required | Your company doesn't have an active subscription or trial. |
| 403 | insufficient_scope | The key doesn't have access to this endpoint, for example a read-only key creating a record. |
| 404 | not_found | The record or endpoint doesn't exist. |
| 405 | method_not_allowed | The endpoint doesn't support this HTTP method. |
| 409 | conflict | The record already exists. details.id is the existing record's id. |
| 429 | rate_limited | Too many requests. Wait for the number of seconds in the Retry-After header. |
| 500 | internal_error | Something went wrong on our side. We're notified automatically. |
What's not included
Demo projects and invoices, and deleted projects, are never returned. Draft invoices are only returned when you ask for them (see Invoices).
Endpoints
Company
GET /v1/me
Returns the company your key belongs to. This is a good way to check that a key works.
{
"company": {
"id": "cm1a2b3c40000abcd",
"name": "Studio North",
"currency": "aud",
"timezone": "Australia/Sydney"
},
"apiKey": {
"id": "cm9z8y7x60000abcd",
"name": "Zapier",
"scopes": ["read"]
}
}scopes is ["read"] for a read-only key and ["read", "write"] for a read and write key.
Projects
GET /v1/projects
Lists your projects. Also accepts status (active or archived).
GET /v1/projects/{id}
Returns a single project.
{
"id": "cm2p0000000000abcd",
"name": "Bondi House",
"status": "active",
"currency": "aud",
"address": {
"line1": "12 Campbell Parade",
"line2": null,
"city": "Bondi Beach",
"state": "NSW",
"postcode": "2026",
"country": "Australia"
},
"client": {
"name": "Jane Smith",
"emails": ["[email protected]"],
"phone": "+61 400 000 000",
"address": {
"line1": "12 Campbell Parade",
"line2": null,
"city": "Bondi Beach",
"state": "NSW",
"postcode": "2026",
"country": "Australia"
}
},
"startDate": "2026-02-01",
"endDate": null,
"createdAt": "2026-01-15T03:12:45.000Z",
"updatedAt": "2026-03-02T22:01:10.000Z"
}Suppliers
GET /v1/suppliers
Lists your suppliers.
GET /v1/suppliers/{id}
Returns a single supplier.
{
"id": "cm3s0000000000abcd",
"name": "Coastal Tiles",
"email": "[email protected]",
"phone": "02 9000 0000",
"website": "https://coastaltiles.com",
"notes": null,
"tradeDiscountPercentage": 15,
"address": {
"line1": "4 Harbour Road",
"line2": null,
"city": "Alexandria",
"state": "NSW",
"postcode": "2015",
"country": "Australia"
},
"contacts": [
{
"id": "cm3c0000000000abcd",
"name": "Sam Lee",
"email": "[email protected]",
"phone": null,
"notes": "Trade account manager"
}
],
"tags": ["Stone", "Tiles"],
"createdAt": "2025-11-04T01:20:00.000Z",
"updatedAt": "2026-02-18T05:44:31.000Z"
}POST /v1/suppliers
Creates a supplier. Needs a read and write key. The body uses the same fields as the supplier above, and the response is the new supplier.
curl https://api.inspec.design/v1/suppliers \
-H "Authorization: Bearer insp_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Coastal Tiles",
"email": "[email protected]",
"tradeDiscountPercentage": 15,
"address": { "city": "Alexandria", "state": "NSW" },
"contacts": [{ "name": "Sam Lee", "email": "[email protected]" }],
"tags": ["Stone", "Tiles"]
}'| Field | Description |
|---|---|
name | Required. Up to 100 characters, and unique within your company. |
email, phone, website, notes | Optional. |
tradeDiscountPercentage | Optional whole number from 0 to 100. |
address | Optional. Any of line1, line2, city, state, postcode and country. |
contacts | Optional list of up to 50 contacts, each with a name and optional email, phone and notes. |
tags | Optional list of up to 20 tag names. Tags that don't exist yet are created. |
If a supplier with the same name already exists, you'll get a 409 conflict with its id in details.id. Names are matched ignoring case, so "Coastal Tiles" and "coastal tiles" count as the same supplier.
Products
GET /v1/products
Lists the products in your product library. Also accepts supplierId.
GET /v1/products/{id}
Returns a single product.
{
"id": "cm6d0000000000abcd",
"name": "Carrara Hexagon Mosaic",
"supplier": { "id": "cm3s0000000000abcd", "name": "Coastal Tiles" },
"url": "https://coastaltiles.com/carrara-hexagon",
"notes": null,
"currency": "aud",
"cost": 8950,
"rrp": 12900,
"imageUrl": "https://media.inspec.design/example.jpg",
"isFavourite": true,
"tags": ["Bathroom"],
"fields": [
{ "name": "Finish", "type": "select", "value": "Honed", "text": "Honed" },
{
"name": "Size",
"type": "dimension",
"value": { "width": 300, "depth": 10, "height": 300, "unit": "mm" },
"text": "W 300mm × D 10mm × H 300mm"
}
],
"attachments": [
{
"id": "cm6a0000000000abcd",
"name": "Spec sheet.pdf",
"url": "https://media.inspec.design/spec-sheet.pdf",
"size": 482113
}
],
"createdAt": "2026-01-20T07:00:00.000Z",
"updatedAt": "2026-01-20T07:00:00.000Z"
}Each field has a text version of its value, formatted with your company's measurement unit. The value depends on the field's type:
| Type | Value |
|---|---|
text, select, supplier | A string, or null. |
multi_select | A list of strings. |
property | An object of property names to values, e.g. { "Colour": "White" }. |
dimension | width, depth and height in millimetres (each can be null). |
image | url, name and caption. |
price | quantity and unitCost (in the smallest currency unit, before tax). |
text is empty for image, price and supplier fields.
Invoices
GET /v1/invoices
Lists invoices and credit notes. Only sent ones are returned unless you set status. Also accepts:
| Parameter | Description |
|---|---|
projectId | Only invoices for this project. |
type | invoice or credit_note. |
paymentStatus | unpaid, paid or on_account. |
status | sent (the default), draft or archived. Archived invoices were cancelled before being sent. |
GET /v1/invoices/{id}
Returns a single invoice, including its payments.
{
"id": "cm4i0000000000abcd",
"number": 42,
"displayNumber": "INV-0042",
"type": "invoice",
"kind": "itemised",
"status": "sent",
"paymentStatus": "paid",
"reference": "Kitchen joinery",
"invoiceDate": "2026-03-01",
"project": { "id": "cm2p0000000000abcd", "name": "Bondi House" },
"currency": "aud",
"subtotal": 1000000,
"tax": 100000,
"total": 1100000,
"creditApplied": 0,
"amountDue": 1100000,
"taxBreakdown": [{ "name": "GST", "percentage": 10, "amount": 100000 }],
"createdAt": "2026-02-27T23:15:00.000Z",
"updatedAt": "2026-03-10T04:02:12.000Z",
"payments": [
{
"id": "cm5p0000000000abcd",
"invoice": { "id": "cm4i0000000000abcd", "displayNumber": "INV-0042" },
"projectId": "cm2p0000000000abcd",
"date": "2026-03-10",
"method": "Bank transfer",
"notes": null,
"origin": "standard",
"currency": "aud",
"amount": 1100000,
"isReversed": false,
"reversedAt": null,
"createdAt": "2026-03-10T04:02:12.000Z",
"updatedAt": "2026-03-10T04:02:12.000Z"
}
]
}kindisitemised,instalmentordesign_fees.amountDueis thetotalminus anycreditAppliedfrom the client's credit on file.- Credit notes have negative amounts and a
displayNumberlikeCN-0003. Apaidcredit note was refunded to the client, and anon_accountcredit note was kept as credit on the client's account. - Drafts are still being prepared, so their lines and totals can change until they're sent. Avoid importing them into your accounting software. A draft's totals can also change without its
updatedAtchanging, for example when the project's tax rate or fees are edited, so fetch drafts again rather than relying onupdatedSince.
Payments
GET /v1/payments
Lists client payments against sent invoices. Also accepts invoiceId and projectId. Each payment has the same shape as in the invoice example above.
amountis the cash the payment settled. Refunds of credit notes are negative.originisstandardfor payments you recorded,credit_on_filewhen the invoice was paid from the client's credit, andon_accountwhen a credit note was kept on account. Onlystandardpayments move money, so the others have anamountof 0.- Reversed payments are included with
isReversed: trueand anamountofnull. Leave them out when adding up totals. Reopening an invoice as a draft reverses its payments and moves them out of this list, so they only appear on the invoice itself.