Inspection Support Network
Developer documentation / Guides / Schedule an order
How to schedule an order
Eight calls against the ISN API, two of them writes. Find the client, find an inspector's open time, create the order as a draft, read it back, then schedule it. Along the way: the three things the API will not check for you.
https://{host}/{companyKey}/rest
Every request is HTTP Basic: the user's access key as the username and
secret access key as the password, generated in ISN under
Settings → My Access Keys. Keys do not expire and carry that user's full
privileges, so the user who owns them needs the CREATE_INSPECTIONS
privilege. Base URLs and company keys are explained on the
home page.
Before you start
- Read
status, not the HTTP code - Most failures come back as
200with{"status":"error","message":"..."}, so check the body on every call. The HTTP code still matters when it is not200:401is bad credentials,403a missing privilege,405a wrong path. An unknown endpoint is a405, not a404. - JSON needs the content type
POSTreads a JSON body only whenContent-Typestarts withapplication/json. Otherwise it reads form fields, and a JSON body arrives as nothing at all, with a200. EveryGETtakes a query string.- Times are on the quarter hour
- Appointment times land on
:00,:15,:30or:45, asYYYY-MM-DD HH:MM:SS. Write them in the company's local time with no offset: the value is stored as written, not converted. - Nothing is reserved
- Creating an order does not consult the calendar. If two systems book the same inspector at the same time, both succeed. An existing order, draft included, does hide its inspector's window from later availability calls, so creating the draft promptly narrows the race; nothing closes it.
- Offices
- A company with one office can skip this. A company with several has two defaults
in play. Lookups (packages, services, clients, inspectors, open time) run against
the office in
?office={officeUUID}, or the company's primary office. The order itself belongs to the office inofficeuuidin its payload, or the key owner's office. The services on an order are checked against the lookup office, so when the two differ, services are silently dropped. The rule: put the same UUID in?office=on every call in the flow,POST /orderincluded, and inofficeuuidon the order. An unknown office UUID is not an error; it quietly becomes the primary office.GET /officeslists them (owner privilege).
The sequence
Examples use the domain 4isn.com and the company key yourcompany,
with the keys in $ACCESS_KEY and $SECRET_ACCESS_KEY. Identifiers
are UUIDs and are shortened here.
Check whose key you hold Read
The order will be created as this user, in this user's office, with this user's privileges. If it is not who you expect, stop here.
Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ https://4isn.com/yourcompany/rest/me
Response
{ "status": "ok", "me": { "id": "9c8b…", "displayname": "Sam Okafor", "inspector": "Yes", … }, "message": "" }Reference: GET /me
Load the catalogue Read
An order names an order type and a list of services. Both are the company's own records, so read them once and cache them. You need one order type id and the id of each service the customer is buying.
Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ https://4isn.com/yourcompany/rest/ordertypes curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ "https://4isn.com/yourcompany/rest/services?withAssociations"
Response (services)
{ "status": "ok", "message": "", "services": [ { "id": "5e4d…", "name": "Residential Inspection", "inspectiontypeid": "6f1c…", "fees": [ { "id": "b21a…", "name": "Residential Inspection", "amount": 450 } ], … }, { "id": "1a2b…", "name": "Radon Test", … } ] }withAssociationsadds each service's default fees and the inspectors qualified to perform it. The fee is a default, not a quote: the price on the order depends on the property, so use it for display and read the priced order back in step 7. The list includes services the company hides from its own online scheduler, marked"visible": "No"; filter them out of a customer-facing form.ancillarymarks add-ons. Refresh the cache with?after=<UTC datetime>to fetch only what changed.If the company sells packages, read those too. A package is a named bundle of services, and all it carries is their ids.
Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ https://4isn.com/yourcompany/rest/packages
Response
{ "status": "ok", "message": "", "packages": [ { "id": "c4a7…", "name": "Buyer's Complete", "description": "…", "active": true, "show": true, "sequence": 1, "services": [ "5e4d…", "1a2b…" ], … } ] }A package is a label, not a shortcut. It has no price, and no other endpoint expands it for you:/availableslotsdoes not accept one, and the order does not add a package's services when you name it. Everywhere below that asks forservices, send the package'sservicesplus any add-ons the customer picked, de-duplicated. To show a package price, add up its services' default fees, or price the real property withPOST /calculateservicequote. Package discounts and modifiers set up for ISN's own online scheduler are not applied through the API.The list includes inactive and deleted packages. Keep only those with
activeandshowboth true. There is no single-package endpoint; read the list and cache it. Standalone fees live at/fees.Reference: GET /ordertypes · GET /services · GET /packages
Find the client, or create them Write
Look the client up by email first. A miss is a
200withstatus"error"and an emptyclient, not an HTTP error.Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ https://4isn.com/yourcompany/rest/client/lookup/jamie%40example.com
Response on a miss
{ "status": "error", "client": "", "message": "could not locate a client with the provided email: jamie@example.com" }On a miss, create the client and keep the
idfrom the response. Creating does not de-duplicate, which is why the lookup comes first.Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{ "firstname": "Jamie", "lastname": "Rivera", "emailaddress": "jamie@example.com", "mobilephone": "208-555-0142" }' \ https://4isn.com/yourcompany/rest/clientResponse
{ "status": "ok", "id": "0b7e…", "modified": "2026-09-22T20:05:11+00:00", "message": "" }The order call also accepts an inline
client: { name, email, mobile }that finds or creates by display name. Prefer the explicit lookup: you get the id back, you control the match, and a renamed client does not become a duplicate.Reference: GET /client/lookup · POST /client
Agents, if you have them Read
Buyer's and seller's agents go on the order as ids only. Look each one up by email the same way; the miss message is
could not locate an agent with the provided email.Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ https://4isn.com/yourcompany/rest/agent/lookup/a.chen%40example.com
An agent id the company does not have is silently dropped from the order rather than rejected, so on a miss leave the field off instead of guessing. Creating agents needs the owner privilege, which an integration key often lacks; let the company add the agent in ISN.
Reference: GET /agent/lookup
Find open time Read
Ask for the inspectors who can do these services in this ZIP over the next few days. Inspectors are filtered two ways: by where they work (the ZIP) and by what they are qualified to do (every service you list). Send
services[]with the brackets; a bareservices=is ignored. With a package, list its services and the add-ons, not the package id: there is nopackagesparameter.Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" -G \ --data-urlencode "zip=83702" \ --data-urlencode "services[]=5e4d…" \ --data-urlencode "services[]=1a2b…" \ --data-urlencode "daysahead=7" \ https://4isn.com/yourcompany/rest/availableslots
Response
{ "status": "ok", "message": "", "count": 2, "zip": "83702", "daysahead": 7, "offset": 0, "services": [ "5e4d…", "1a2b…" ], "slots": [ { "userid": "9c8b…", "start": "10/6/2026 08:00", "end": "10/6/2026 17:00", "userdisplay": "Sam Okafor", "userfirst": "Sam", "userlast": "Okafor" }, { "userid": "9c8b…", "start": "10/7/2026 08:00", "end": "10/7/2026 12:00", … } ] }Each slot is an inspector's whole working window for that day, not an appointment. A window is listed only when nothing overlaps it: no blocked time, no calendar event, no order, drafts included. A window with one booking in the middle disappears entirely; you never get the fragments either side. You choose a start inside a listed window and send the length asduration. A three-hour job that starts at 16:00 in a window that ends at 17:00 will be accepted.Other things to know: dates are
M/D/YYYY HH:MM, not ISO. Only inspectors who can perform every listed service appear. The ZIP filter applies only when the company has service areas set up; otherwise every inspector is returned. Same-day booking and the last bookable hour are company settings, so an empty list for today can be policy rather than a full calendar. Leaveservices[]off and you get everyone in the ZIP, qualified or not.offset=Nstarts N days from today;inspector=<userUUID>narrows to one person, but an id the company does not have narrows nothing and you get everyone, so check theuseridon what comes back.GET /users?availableonlylists the people who have working hours at all.Reference: GET /availableslots · GET /users
Create the order as a draft Write
One
POSTwith the property, the appointment, the people and the services. Leaveinprogressout. The order is saved in progress: it exists and has an order number, but it is not on the calendar, sends no automatic email or SMS, and does not fire the scheduled webhook. Nobody is told anything yet.Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d @order.json \ https://4isn.com/yourcompany/rest/order
order.json
{ "address1": "1428 Elm Street", "city": "Boise", "state": "ID", "zip": "83702", "squarefeet": 2150, "yearbuilt": 1998, "datetime": "2026-10-06 09:00:00", "duration": 180, "inspector1uuid": "9c8b…", "ordertypeuuid": "6f1c…", "services": [ "5e4d…", "1a2b…" ], "packages": [ { "package": "c4a7…" } ], "clientuuid": "0b7e…", "buyersagentuuid": "a3d5…", "notes": [ { "owner": "9c8b…", "text": "Gate code 4412. Dog in the yard." } ] }Response
{ "status": "ok", "id": "3f9c…", "oid": 23522, "modified": "2026-09-22T20:06:40+00:00", "message": "" }Keep both identifiers.
idis the UUID every API call wants;oidis the order number the company's staff see in ISN.servicesis the complete list;packagesis only a record. Anything not inservicesis not on the order and is not priced, including the services of a package you name inpackages. So with a package,servicesis the package's services plus the add-ons, de-duplicated (a repeated id is harmless), andpackagesnotes which bundle the customer chose. Leavepackagesoff when there is none. A package id the company does not have is a problem: the order is still created, but the response comes back"status": "partial"and the services and fees after it were never attached. Keep theid, do not create again, and fix the order with the update call.Fees are priced from the catalogue for those services unless you send
donotcalculateservicefees: true. To override a price, addfees: [ { "feeid": "b21a…", "value": 425 } ]; an unknownfeeidfails the whole request.statetakes an abbreviation or a name.inspector1uuidis theuseridfrom the slot you chose;inspector2uuidand up add more people. In a multi-office company addofficeuuidand put the same id in?office=on the URL (see Offices above). Every other field is optional and listed in the reference.Reference: POST /order
Read it back Read
Confirm the appointment, inspector, client and services landed the way you sent them. An id that the company does not have was dropped without an error, and this is where you find out.
Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ https://4isn.com/yourcompany/rest/order/3f9c…
Response
{ "status": "ok", "message": "", "order": { "id": "3f9c…", "oid": 23522, "datetime": "2026-10-06 09:00:00", "duration": 180, "scheduleddatetime": "", "inspector1": "9c8b…", "client": "0b7e…", "buyersagent": "a3d5…", "ordertype": "6f1c…", "services": [ … ], "fees": [ … ], "totalfee": 550, … } }An empty
scheduleddatetimeis what a draft looks like. This is the moment to show the customer a summary, take payment, or hand off to a person. If you need to notice a double booking, repeat step 5 for this inspector and day before you go on; it is coarse, but it is the only check there is.Reference: GET /order/{id}
Schedule it Write
Update the order with
inprogressfalse and ascheduledtimestamp, normally now. This is the call that puts the inspection on the calendar.Request
curl -u "$ACCESS_KEY:$SECRET_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{ "inprogress": false, "scheduled": "2026-09-22 14:06:00" }' \ https://4isn.com/yourcompany/rest/order/3f9c…Response
{ "status": "ok", "id": "3f9c…", "oid": 23522, "modified": "2026-09-22T20:07:02+00:00", "message": "" }From here on, people get told. The company's automatic email and SMS events for a new order become eligible, theORDER_SCHEDULEDwebhook fires, and the inspection appears on the inspector's calendar and in footprints. While you are testing, put"sendemailevents": false, "sendsmsevents": falseon the draft in step 6 and nothing goes out, now or later: the flags stay on the order until you set them back totrue.Both fields, every time: on this call
inprogress: falsewithoutscheduledis not refused, it is ignored, and the order stays a draft with a200. Read it back if you need to be sure.scheduledbydefaults to the user whose key you hold. Anything you left off the draft can be sent in this same call, but do not repeatservicesorpackagesthat are already on it: resent services are priced again and the amounts add to the fees already there, and a resent package is recorded a second time. The calendar entry needs both an appointmentdatetimeand an inspector; a scheduled order without them is scheduled in status only. A repeat of this call fires the scheduled webhook again, so do not retry it blindly.Reference: POST /order/{id}
One call instead of two
If nothing happens between creating and scheduling, add the two scheduling fields to the draft payload in step 6 and skip steps 7 and 8. The order is created already scheduled, and the notifications go out at once.
Draft, then schedule
Use when a person confirms in ISN, when you collect payment first, or when you
want to read the priced order back before anyone is told. This is also how the
ISN MCP server's create_order and schedule_order tools
behave, so the two surfaces describe one model.
Scheduled in one call
Use when your system has already taken the booking and ISN is the system of record. One thing to know: on this path the scheduled webhook fires before the services and fees are attached, so a webhook consumer that reads the order at once can see it unpriced. Add these to step 6:
"inprogress": false, "scheduled": "2026-09-22 14:06:00"
Building a booking form
A customer-facing form usually asks for the property, an inspector, a package, add-ons and a preferred time. Each part maps onto the calls above.
- Package picker
GET /packages, keeping those withactiveandshowtrue. Show each package's services by matching itsservicesids againstGET /services?withAssociations, and show a price as the sum of their default fees.- Add-on list
- The same services list, without
"visible": "No". A service whose id is in the chosen package'sservicesis already included; mark it that way rather than letting it be added twice. - What gets sent
- One list of service ids: the package's plus the add-ons. It goes to
/availableslotsasservices[]and to the order asservices. The package id goes only to the order, inpackages. - Inspector picker
- Easiest with the services chosen first:
/availableslotswith the ZIP andservices[]returns only inspectors who cover the ZIP and can do all of it, and you group the slots byuserid. If the inspector comes first, start from/availableslotswith just the ZIP. Then offer only packages and add-ons whose services all list that inspector in theirinspectors, and check again withinspector=andservices[]once the selection is made. - Preferred date
/availableslotshas no date parameter. For one day, sendoffsetas the number of days from today anddaysahead=1. An order has a start time, not a window: send a quarter-hourdatetimeandduration, and put "10 AM to 12 PM" in a note if the window matters.- "The inspector will confirm"
- That is the draft: create it in step 6 and stop. The company sees it in ISN and schedules it, or your system does step 8 later.
- Several offices
- Every call above takes the same
?office=, and the order also takesofficeuuid. See Offices at the top.
Errors you will meet
the datetime object returned an unround time- The appointment is not on a quarter hour. Round it and resend. A time the server
cannot parse at all answers
please provide a valid date/time format. please provide a scheduled date/time format- You sent
inprogress: falsewithoutscheduledon the create call. Add a timestamp, normally now. The update call does not complain about the same omission; it just leaves the order a draft. Unable to lookup Fee- A
feeidinfeesis not one of the company's fees. The whole request fails; nothing was saved. Ids come fromGET /feesor thefeeson each service. could not locate a client with the provided email- Not an error in the HTTP sense, just a miss. Create the client (step 3).
403this action requires user access: CREATE_INSPECTIONS- The key's user cannot create orders. The company grants the privilege in ISN, or issues keys from a user who has it.
400this action requires the following fields- Only a few endpoints check for required fields up front, address validation among them. The message names what is missing.
405missing or invalid action specified- The path is wrong. An unknown endpoint answers
405rather than404, so a typo in an endpoint name looks like a method problem.
What comes next
- Reschedule
POST /order/{id}with a newdatetime, and a newinspector1uuidif the person changes. - Confirm
POST /order/{id}withconfirmedset to a timestamp once the client has confirmed the appointment. - Notes, fees, history
GET /order/notes/{id},GET /order/fees/{id},GET /order/history/{id}. - Complete
POST /order/complete/{id}when the inspection is done. This fires the order-completed events and webhook. - Reports and files
PUT /orders/addreporturlto attach a hosted report,PUT /orders/uploadreportandPUT /orders/uploadattachmentto upload one. These take form fields or a raw file body; see the reference. - Remove
DELETE /order/{id}. - Price first
POST /calculateservicequotereturns the priced fees for an address, square footage and services before any order exists. Owner privilege. - Validate the address
GET /geocodewith all four ofaddress1,city,stateandzipreturns the corrected address and coordinates. - Hear about changesFootprints (on the home page) tell an integration about upcoming work without polling the order list.