Projects — Bulk Multi-Site Contracts
Stage a client's site list, generate a ticket per site, and route them together
Projects are for bulk site visits that arrive in bulk as a spreadsheet or large text files: one contract, one standard scope of work, and dozens or hundreds of sites that each need their own ticket and their own technician. Instead of building those tickets one at a time, you stage the whole site list in one place, generate the tickets in a batch, and route them to the marketplace together.
A project lives under a client record. It works the same whether you're a service company managing a project for your customer, or you're the client company managing your own rollout — the model is identical.
1. Turn Projects on for a client
Open the client record and enable Projects. Once at least one client has projects enabled, Projects appears in your main navigation. If you don't see the nav item, no client has it switched on yet.
2. Create the project and set the scope
From Projects, create a new project, give it a name, and pick the client. A project has two tabs: Sites, the grid you work from every day, and Setup, where the project's settings live. On Setup, fill in what every site will inherit, then click Save Project:
Scope of Work — the standardized description every generated site visit receives. Write it once; every site gets it. To put a site's own value in the text, click the place in the scope where it belongs, then click that variable under Insert a site variable. It goes in as the column's name followed by its placeholder,
# of Cameras: {{# of Cameras}}, and each site visit shows that site's number there, for example "# of Cameras: 6". The name is ordinary text, so you can reword it ("Cameras to mount: {{# of Cameras}}"); only the part in braces is filled in. Each variable goes on its own line, and the cursor moves to the next line, so you can click down the list to stack them. Add all inserts every variable not yet in the scope at once, one per line. A variable already in the scope turns green; click the × on it to take it back out, name and all. A variable you insert isn't listed again below the scope; the ones you don't insert still are. A site with no value shows "(not listed)", and a 0 shows as 0. Only variables the technician sees can be inserted. The Preview under the box shows how the first site will read.Visit Defaults — who does the work, the arrival type, the repair type, the default schedule (a single date and time, or a start/end window), the Client Bill Rate and, for a marketplace project, the Tech Pay offer. These become the starting point for every ticket the project generates.
Marketplace or your own employee. Under Who does the work, set Assignment to Tech Marketplace (post for outside techs) or Our employee. An employee project needs a Default employee: every generated site visit is assigned to that person, and you reassign each visit afterwards (a placeholder employee works fine). An employee project has no Tech Pay, and Post to Marketplace and Message Techs don't appear on it — the marketplace parts of this article apply to marketplace projects only.
A project can be Active, Completed, or Archived, so finished work stops cluttering the list.
3. Get the sites into the project
There are four ways in, and most real projects use more than one. On the Sites tab, the Add Sites menu holds Add Existing Ticket, Import Sites (CSV / paste) and Ticket Numbers. Client work-order PDFs and dispatch emails fill in sites that are already in the project, so they live under Edit → Import Ticket Files (PDF / Email), which takes PDFs, .eml files, or both in one upload.
Import Sites is the main path. Upload the client's CSV/Excel file or paste the rows straight out of an email, then map the columns — telling Service Envoy which column is the site name, street address, city, state, ZIP, date, time, and (if the client supplied them) their ticket numbers. Service Envoy suggests the mapping automatically and you correct anything it got wrong.
Keep the extra columns. Client site lists almost always carry columns that matter to the tech but don't fit a standard field: device counts, cable type, ceiling height, survey links, badge requirements. On the mapping step every one of these starts ticked as a Per-Site Variable; untick any you don't want. Each kept column becomes its own sortable column in the project grid, and its values are added to that site's scope of work so the tech sees them on the job. Zeros are included, so the tech can see what a site doesn't need as well as what it does.
Internal only. Some columns are for you, not the tech, such as a price per store. Tick them under Internal only on the mapping step (columns that look like pricing start ticked) and they stay on the grid and on the site visit, but never appear in the tech's scope. You can change this later with the lock on each variable under Per-Site Variables; save, and choose to update the site visits already generated.
Shown once assigned. A variable the tech should see only after they get the job, not while browsing Available Work, is switched with the people icon on that variable under Per-Site Variables. Technicians looking at the visit before it's assigned see "(shown once you're assigned)" where it was placed, and it's left out of the list below the scope; the assigned technician sees the value. The preview under the Scope of Work shows both versions. Save, and choose to update the site visits already generated. If you later edit a visit's scope by hand, the whole scope shows on Available Work again until the project updates it.
Nothing the client sent is lost. Every row is saved with its site exactly as the client sent it, every column, whether you mapped it, kept it or not. Open a generated site visit and the Site Details section, under the visit scope, lists the site's variables (internal ones marked Internal) and, under The client's original row, every column from the spreadsheet. Links are clickable. Site Details is shown to your company only, never to the technician.
The next sheet maps itself. Service Envoy remembers how you mapped a client's spreadsheet. When the same client sends another list with the same columns, for this project or a new one, the mapping, the kept columns and the internal-only choices are filled in for you. Columns it hasn't seen before are kept, ready for you to review.
One thing the importer handles for you: spreadsheets routinely turn a ZIP like 04401 into the number 4401. Service Envoy puts the leading zero back, because a mangled ZIP has no location and the site would silently vanish from technician radius searches.
Duplicates are caught before anything is created. If a row in your list is a site you already have, the import screen says so and shows you which one it matched — checking both the sites already on this project and the rest of the list you're importing, so a file that repeats itself is caught too. A row is called a Duplicate when the client's own site number matches, or when it's the same street address in the same city or ZIP. It's called a Possible match on looser evidence — same city, state and ZIP with no street address to compare, or the same building number on a differently-spelled street.
Every flagged row keeps a tick box, and the tick decides whether it imports. Duplicates arrive unticked, so re-pasting a list you've already imported does nothing by default. Possible matches arrive ticked and import normally — they're a heads-up, not an accusation. Skip all and Import all set every flagged row at once, and the Import button tells you exactly what you're about to create: Import 38 sites of 40. A second visit to the same address is perfectly legitimate — a return trip, a different suite, a later phase — so nothing is ever blocked. You're told before it happens, instead of finding the duplicate ticket afterwards.
Import Ticket Files with PDFs is for the client who sends a stack of work-order PDFs rather than a spreadsheet. Upload them all at once; each is read automatically and matched to a site by address, city, and state (not by work-order number — you don't have those yet at import). You review every match before anything is applied, and can re-point a mismatched one at the right site. On apply, the PDF becomes the source of truth for that site's ticket number, scope, schedule, and address.
There's one decision worth understanding on that screen: "Set each PDF as a fill-out task on the visit." Tick it and the technician gets a required Fill out… task that opens the PDF in the in-app editor and has to be completed on the job — use this when the client's form must come back filled in. Leave it unticked and the PDF is simply attached to the visit's files for the tech to read, which is what most email-based clients want.
Duplicates and collisions are flagged here too. If a PDF is set to create a new site but the project already has that place — matched on the client's site number, or the same street address in the same city/ZIP — the row turns amber and says which site it matched, with a Use this site instead button that re-points the PDF in one click. Two PDFs in the same batch that would both stage a new site for the same place are caught the same way; the server can't see that one on its own, because it scores every PDF against the sites that existed when you uploaded.
A second warning covers a different mistake: two PDFs aimed at the same existing site. Applying writes the WO#, scope, schedule and address onto that site once per PDF, so whichever lands last wins and the other one's fields are quietly lost. The review screen now names the other file so you can re-point it, skip it, or untick the fields it shouldn't overwrite. Nothing is blocked in either case — two work orders at one address is real work sometimes — you're just told before you apply rather than after.
Import Ticket Files with emails handles the client who dispatches by email. Upload the .eml files and each is matched to a site by the address in the email. Review the matches, then attach — the email's photos and its body text (saved as a .txt) land in that site visit's files, so the tech gets the original dispatch instead of a summary of it. When an email contains a Teams, Zoom, Webex or Google Meet link, its review also offers Add a Join Meeting task (ticked), which puts a Join Meeting task with that link on the site visit. Join Meeting isn't offered in the project's Edit Tasks, because it needs a link for each site visit.
Add Existing Ticket covers the ticket that belongs to this project but came in through the normal queue — someone emailed it in, or it was keyed by hand before the project existed. Rather than re-typing the work, search this client's existing tickets by ticket number, site name, address, city, or state, and link one in as a project site. It then behaves exactly like a generated site. Searching is limited to this project's client on purpose: a project lives under one client, and pulling in another client's ticket would attach the wrong billing and rate context.
4. Review the grid
Imported sites land as Staged rows — nothing has been created yet. The grid shows each site with its location, your per-site variables, its date/time, and its status. Sites without a scheduled date are flagged, because a date is required before a visit can be posted to the marketplace.
You can add ticket numbers at this stage too. If the client supplied their own work-order numbers, map that column at import or use Add Sites → Ticket Numbers to apply them in bulk. Any site without one still generates fine — Service Envoy assigns its own number, and you can fill in the client's later.
Selecting rows without hunting for them. Almost every action below works on the rows you've checked, so there are shortcuts next to the Sites count for the two selections you'll want most: Routable (N) picks every generated visit that can still be posted (has a date, not yet assigned), and Staged (N) picks every row that hasn't generated a ticket yet. Clear drops the selection. Combine these with the column filters — filter to one date or one state first, then select — and you can act on exactly the slice you mean.
Export CSV downloads what you're currently looking at: the rows as filtered and sorted, not the whole project. Filter first if you want a subset.
5. Generate the tickets
Select the staged rows you want and click Generate Tickets. Each selected site becomes one ticket with one site visit, carrying the project's scope of work, that site's variables (except internal-only ones), its address, its schedule, and the project's pay rate.
Once generated, the row stops being staged and starts showing live status — and the Site column now lists that ticket's site visits as links. Click one to open the site visit; the small icon beside it opens the visit in a new tab so you can keep the project list open while you work. If a ticket ends up with more than one site visit — a second trip to finish the work, say — every visit is listed with its own date, so nothing on the project is hidden from you. On the Site Visits list, each of those visits carries a Project: <name> badge under its ticket number — click it to jump back to the project.
Site visits are the links here rather than the tickets on purpose: until a technician is assigned, the visit is where everything happens — routing, offers, scheduling, and pay. The parent ticket is always one click away from inside the visit.
6. Post the visits to the marketplace (marketplace projects)
Select the generated rows and click Post to Marketplace to put them in front of technicians as a batch. The button shows how many of your selected rows can actually be posted — a row needs a generated visit with a scheduled date and no technician assigned yet, so staged rows and already-assigned visits are quietly excluded from the count. From there each visit follows the normal marketplace flow — offers, requests, counters, and assignment — covered in Finding & Routing Technicians.
The project grid keeps a live status per site so you can work a large project from one screen:
Staged — imported, no ticket generated yet
Routed — posted to technicians and waiting
A request count on any row where technicians are waiting on your decision
The assigned technician, once someone has taken the visit
Reading the Date / Time column
The date shown always comes from the site visit itself, not from the project's default window — once a visit exists, it is the record of truth for scheduling, and you may well have moved it since. Before a site is generated, the column shows the staged date, or the project's default window in grey italics when the site is simply inheriting it. A generated visit with no date reads no date in amber, because that's the one thing that will block posting. When a ticket has several visits, the column shows the earliest and marks how many more there are.
Changing visits after you've generated them
Three tools, and the difference between them is what they touch.
Edit → Edit Selected changes exactly the rows you've checked, and nothing else. The dialog is built from independent sections — site name, schedule & arrival type, repair type, scope of work, client bill rate, technician pay, tasks, and files — and only the sections you switch on are applied. Everything else on those visits is left as it is, and unselected visits are never touched. This is the one to reach for when a date slips on nine sites, or one region needs a different rate. A site visit that an outside technician has already accepted keeps its schedule: Edit Selected skips it and tells you why. Use Request Schedule Change on that visit instead.
Edit → Edit Tasks opens Project tasks, which has two lists. Tasks new visits inherit is the checklist every site visit generated from this project starts with, on top of the client's own default tasks. The up and down arrows beside each task set the order each new site visit’s checklist starts in; technicians see tasks in that order. Remove here only changes what new site visits get. It never takes the task off visits that already exist.
On the site visits already generated lists every task that is on this project's existing site visits, how many visits have it, and how many have already done it. Remove from visits takes that task off them, after you confirm. A task that's already been completed stays, and so does any visit that's been submitted for approval, billed, closed or cancelled. To stop a task completely, remove it from both lists.
To add a task, type its description or pick its type and timing, then choose where it goes. Both boxes are ticked to start with: Save to the project template (every site generated from now on starts with it), and Add to the N site visits (the ones you've selected, or, with nothing selected, the ones your filter shows, or every visit already generated). Add another queues another task without closing the dialog, and Save N tasks saves them all. An untouched form adds nothing, so you can open the dialog just to remove something and close it again. Fill out PDF and Join Meeting aren't offered here, because each needs a PDF or link per visit.
Edit → Fix Missing ZIP / City / State fills in the part of an address your client's spreadsheet left out. A site with no ZIP has no spot on the map, and its distance to technicians is only a rough guess from the town's center. The menu shows how many sites can be fixed. With the street, city and state it finds the ZIP; with the street and ZIP it finds the city and state. A review list shows what it found for each site before anything is saved. Sure matches are ticked for you. Rows marked Check matched something slightly different, like the other side of the street or only the street and not the building, so look before ticking them. When a town has several ZIPs and none could be matched, pick one from the list. Click Fill in to save. Blank parts are filled, and the client's saved site gets the same fix. If the lookup spells the town differently (like W. Falls for Wichita Falls), the row shows the new spelling, and ticking it changes the town too. Nothing else already entered is changed.
Edit → Project Files is for files the client gave you that every site visit needs, like a scope of work document. Choose the files, set who sees them, and click Add files to all site visits. They go on every site visit already generated, and on each new one when it's generated. Visible to the assigned tech lets the technician doing the visit open them. Visible on Available Work lets technicians see them before anyone is assigned, which also makes them visible to the assigned tech. You can change either choice later, or remove a file, and it changes on every site visit at once. Removing a file from one site visit takes it off only that visit.
Changing the project's scope of work or schedule happens on the Setup tab. When you click Save Project after changing the scope of work or the schedule (dates and times, arrival type, hard-start requirement), a dialog asks which existing site visits to update as well: All site visits (including anything already routed to the marketplace), Only un-routed site visits (staged sites and tickets not yet posted — routed work is left alone; this one is picked to start with), or Only imported sites without tickets yet. Just save the template changes only what new site visits get. A visit that isn't updated keeps the settings it was created with, and a site visit an outside technician has already accepted always keeps its schedule.
Knowing what it will cost to accept
Funding Needed answers the question you actually have when technicians start requesting a big batch: if I accept these, what do I need in my balance? It covers every site visit with an outstanding technician request and prices each one the way an accept really charges — that technician's labor (their counter-offer if they countered, otherwise your posted rate) plus their travel pay. Because several technicians can be in contention on the same visit at different prices, it shows you the range, from accepting the cheapest request on each visit upward.
By default it follows your current selection or filter, and it says so at the top — the number is only useful if it's obvious which visits it covers. One click switches between your filtered slice and the whole project. If nothing in your current slice has an outstanding request, it tells you that rather than showing a misleading zero.
Messaging the technicians on a project (marketplace projects)
Send → Message Techs sends one message to the technicians involved in the project — the assigned technician on assigned visits, and every applicant or counter-offerer on the ones still open. It's the fast way to push out an instruction (a badge requirement, a parking change, a schedule shift) without opening every visit one at a time.
To reach only some of them, tick those sites first. The button then shows the count — Message Techs (3) — and the dialog asks whether to message just those sites' technicians or every technician on the project. This is what you want whenever the message only applies to one date, one region, or one client's batch: filter the grid down to those rows, select them, and nobody else hears from you. On a seventy-site project that's the difference between a useful note and spamming everyone.
A second dropdown, Send to, narrows by involvement rather than by site: all technicians, only the assigned ones, or only those still in contention (requested, applied, or countered). The two combine, so "assigned technicians only, on these three sites" is a single message. One thing worth knowing: once a visit is assigned, the technicians who applied to it are never messaged about it again — they're no longer relevant to that visit, whichever option you pick.
Messages go through Service Envoy's private relay. Technicians see them in Marketplace Messages and by email, and their address stays hidden from you. Don't include phone numbers, emails, or links — the relay blocks contact information until work is assigned.
Sharing progress with your external customer
Throughout this section, your external customer is the company the project belongs to — the client record you picked when you created it. They're called that here to keep them distinct from the marketplace sense of "client", which means the company posting work to technicians.
Send → Sharing Options creates a rolling link you can send to your external customer — including one who has no Service Envoy login. Under Email to, tick as many of the client's contacts as need it, or type any other address and click Add; each person gets their own email with the same link. The Email button on an existing link works the same way. Tick Require an emailed code to open and the page asks for an email address and a 6-digit code the first time it's opened on a new phone or computer, so a forwarded link alone won't open it; an existing link has the same switch, with Opened by showing who has entered a code (see Sharing with Your Customer). It stays current as the project runs, so new visits and their completed deliverables appear automatically without you re-sending anything. Links expire after 30 days, and you can copy, revoke or delete existing links at any time from the same place. The page's browser tab is titled with just the project name, so it reads as their project when they bookmark it or switch back to it.
What stays hidden until you send an approval
Your external customer does not see everything you see. Until you have sent them a visit's price for approval, the shared page shows neither that visit's client rate nor the technician requests on it — the visit simply reads Awaiting Tech Requests, with the project's available window, even while technicians are requesting it on your side. That keeps the negotiation with technicians inside your company until you've decided what to put in front of your customer.
The moment you send a visit for approval — by any of the routes below — its rate and requests appear on their page. If you never send one, they never see a price for that visit, which is the right outcome for work you bill some other way.
Choosing what a project link shows
The project's Sharing Options has the same tickboxes as a site visit or ticket: Technician deliverables, Site visit details (status, schedule, address, scope of work, task checklist, client provided files, and each visit's rate on the project page), Assigned technician (once a technician is assigned, their name and contact details; before that, only how many technicians have requested each visit, never who) and Repair notes. A project page has no live activity timeline. The boxes start ticked the way the client's Sharing Defaults say, and each link in the list shows what it includes and can be changed in place on the same URL. Links made before these tickboxes keep showing the full page.
Your customer never sees which technicians have requested a visit: that stays with you. They see the count of requests, and the technician's name and contact once one is assigned. Price and date approvals you send them always show, whatever is ticked. Sharing with Your Customer explains every way to share, project links included, in one place.
Letting your external customer approve technician hours
When you create a share link you can tick Share tech times for approval. Your external customer then sees each site visit's technician on-site and off-site times — shown in the site's own local time — and can either approve them or dispute them.
Beneath it sits Let them report missing deliverables. Ticked, your customer can flag a visit's work product as missing or incomplete straight from the shared page — which emails the technician what's short, holds their payout, and blocks approval and invoicing until it's resolved. They can mark it complete again once everything arrives, which releases it. Like tech times, it's off by default and set per link.
Project links have a third option, Let them mark work no longer available. It adds an Already Staffed? Remove from Project button to each site visit, for when your customer has covered it themselves (their end customer cancelling, or the work being rescheduled, are also offered as reasons). Using it cancels the visit: it comes off the marketplace and out of routing, every open technician request is voided and those technicians are told, and any funds you committed return to your balance. Nothing is deleted — the ticket and the visit stay for you to close out. The button disappears as soon as a technician is assigned; from then on it's a call to you. Off by default, set per link.
The two differ in one important way. A customer disputing the technician's hours never changes your record — their claim is filed next to yours and you decide. A customer saying the deliverables are short does change it, because that's not a matter of opinion about what we wrote down: it's the person paying for the work telling you they didn't get it.
This is worth using on work you bill through to a customer. Approving a technician's hours is what releases their pay, and once it's gone it is very hard to claw back if your customer comes back weeks later saying the tech wasn't on site for those hours. Getting your customer's sign-off first puts that conversation before the payment instead of after it.
The setting is off unless you tick it, and each link has its own settings — ticking it on a new link never changes a link you shared earlier. You can also switch any of these options on or off for a link that's still active, from the same dialog; the change takes effect immediately on the link your customer already has. If your external customer disputes a visit's times, we email you straight away, the site visit shows what they say the times should have been along with their explanation, and the technician's payout is held so it can't release while the hours are in question. You then either accept your external customer's times or keep the technician's; either choice closes the dispute and lifts the hold.
Your external customer can filter for what's waiting on them
At the top of the shared project page, under Waiting on you, your external customer gets a one-click Approve technician hours chip carrying exactly how many visits have reported hours they haven't answered yet. Picking it narrows the list to those visits, with a note above reading "N site visits need your time approval". On a seventy-site project that's the difference between acting on it and scrolling past it. It only appears when the link shares tech times and something is genuinely outstanding, and a dispute counts as answered — they've acted either way.
Asking your external customer to do something
Two columns on the project grid tell you where each visit stands with them: Client Time (approved, disputed, or still waiting on the technician's hours) and Rate Approval (where they stand on the price). Filter on either to find everything outstanding, select the rows, and use Send → Email Client to send one email listing those site visits with a direct link to each. The button carries the count — Email Client (12) — and follows your selection, or everything the current filter shows when you've selected nothing.
The dialog asks what you're asking for, and the answer changes what the email does:
Something else (message only) sends your message with a link to each visit and creates nothing for them to answer. Use it to request a purchase order, confirm after-hours access, or chase a sign-off — the email says whatever you write.
Approve updated pricing creates something they owe you an answer on: one pending approval per site visit, at the exact price currently on that visit. This is the one to reach for after a technician has countered.
You write the message either way and it's required — nothing is assumed about what you need. Pick the recipient from the client record's saved contacts, or type any address.
Getting a changed price approved before you commit a technician
When a technician counters on pay and you bill that work through, your external customer's price moves too. Sending the new price for sign-off before you assign anyone puts that conversation before the commitment rather than after it.
For a single counter, do it from the site visit: open it, go to Routing, and click Add to Approval on the technician's countered row. That prices the counter for your customer without matching it — so the other technicians never see the higher number, and nothing on the visit changes until your customer says yes. The full walk-through is under Finding & Routing Technicians.
For a batch, work the Counters column to find the visits where a technician has asked for different terms, set the client rate you want on each, then select those rows and send them with Email Client → Approve updated pricing. That sends whatever client rate is currently on each visit, so it's the route for when you've already changed the price yourself and just need the sign-off recorded.
Two things are checked for you. A visit with no client rate set is refused, with the ticket numbers listed, because there is nothing to approve — price them first or drop them from the selection. And re-sending replaces any request still outstanding on those visits, rather than leaving two prompts on the shared page disagreeing about the price.
Raising one visit on its own. The project grid is for a batch. When it's a single visit — you've just re-priced it and you're standing on it — open the site visit's Financials tab and use the Client Rates card. Two buttons, and both send your external customer an approval request, which shows up under “N site visits need your approval” on the page they already have.
Add Approval Request does exactly that and nothing else — one click, no email. This is the one to reach for on a live project: your customer is already working that banner, and a separate email for every visit is noise. It's greyed out until the visit is shared with them, because there has to be a page to add it to.
Send for Approval does the same thing and emails them a link, with a note from you explaining what changed. Reach for it when the change needs explaining — or when nothing shares the visit yet, because this is the only one that will create a link.
Either way the badge on that visit reads Awaiting client approval until they answer. If they've already answered once and you have since changed the price, the buttons read Update Approval Request and Re-send for Approval — raising it again supersedes the old answer rather than adding a second one.
Taking a request back. While an ask is still pending, a third button appears on the same card: Withdraw Request. It asks you to confirm — it's your customer's to-do list you're editing — and lets you add an optional reason. The visit then leaves the “needs your approval” list on their page, and the withdrawal, who did it, and the reason are recorded on the visit's activity. A request they have already approved or declined is history and is never touched; the button only shows while something is genuinely open.
This all works on a site visit that isn't part of a project at all. If the visit isn't shared with your customer yet, Send for Approval creates a secure link to that one site visit and includes it — not a link to anything else you have. You'll see it afterwards with the visit's other share links, and you can revoke it there like any other.
Choosing what they approve. Both buttons — and Email Client → Approve updated pricing on the project grid — ask what you're sending: Schedule only, Price and schedule, or Price only. Use Schedule only when the date or arrival window moved but the price didn't — your customer sees the new schedule and approves or declines just that, and the price they already approved stays approved. It's only offered when they have approved the exact price the visit bills today (on the project grid, every selected visit); if the price has changed too, send Price and schedule.
If your customer declined a date, the next request always includes the schedule — a declined date is still an open question, so you never have to remember to add it.
An arrival window shows as a window: a visit scheduled for 1:00 PM – 3:00 PM on one day reads exactly that way on your customer's page and in the email, never as a single 1:00 PM start.
The email states the rate structure, never a total. On anything but a flat rate a total is a ceiling: it assumes the technician uses the full first block and every additional hour, which is often not what happens. Quoting that as "the price" overstates what they'll actually be billed, in the direction that costs you trust. They see the structure they're agreeing to, and the invoice follows the real hours.
What your external customer sees
At the top of the shared project page sits one filter, in two rows. The first row, Waiting on you, only appears when something is genuinely outstanding: a chip reading Approve pricing & scheduling — or just Approve pricing when no date is in question — carries the count of visits awaiting their answer, alongside Approve technician hours and Work order paperwork needed when those apply. Picking the pricing chip narrows the list to exactly those visits, because nobody should be asked to agree to prices they haven’t been shown, and a note above the list reads “N site visits need your approval”. From there they open each visit and approve or decline it on its own (their name and email are required). There is no approve-everything button — each price is agreed to one visit at a time. Picking All open work in the second row puts the full list back.
That second row, Where the work is, is the customer's view of the pipeline: Awaiting Tech Requests (posted, nobody has asked for it yet), Requests to Review, Scheduled, Tech Onsite, Work Done, and Invoiced, each with a count, plus an Overdue chip for a scheduled visit whose whole window has passed with nobody arriving. Each visit's badge uses the same words as the chip that finds it. These are deliberately fewer and plainer than the statuses you see on your grid — the customer's question is only ever “has anybody picked this up, and where is it now?”
Each visit shows the rate structure, the previously agreed one struck through where there was one, and your covering note. They can approve, or decline — and a decline makes them say what would work instead ("we can approve up to $500"), because a refusal with no number just costs another round trip. You're emailed either way, and their name, email address and the time are recorded against the visit.
When the price and the date are both in question
A technician's counter can move the schedule as well as the pay, and often does. Where the date has moved too, the same request carries both — and your external customer answers them separately. They may perfectly well take the price and refuse the day; it's their site access.
The card then shows two blocks, each with what's proposed and what it replaced. A committed time reads as a hard start — "Sep 3, 2026, 9:00 AM" — while a span reads as an arrival window — "Sep 2 – Sep 30, 2026, 8:00 AM – 1:00 PM" — so a technician narrowing a four-week window down to one morning is visible as the change it is. Each block carries its own pair of buttons — Approve the price / Decline the price and Approve the date / Decline the date — and beneath both sit Approve both and Decline both for when one answer covers everything.
The visit keeps waiting until every part they were asked about has an answer. A declined part changes nothing on the visit by itself — it's recorded and you decide what happens next, because the right answer might be a different technician, a different date, or a phone call.
When the technician asks to move the date. On a project the date is your customer's call, so when an assigned technician submits a request to move a live project visit, that request is placed on your customer's shared page as a scheduling ask — under the same Approve pricing & scheduling chip, with the technician's proposed window shown against the current one. No email is sent; it simply appears on the page they already have. Their approve accepts the technician's request exactly as if you had accepted it yourself, and their decline declines it. If you answer the technician's request directly first, or the technician withdraws it, the ask on their page retires on its own rather than asking about a date that's already settled. Where they had already approved a price, that approval is carried forward and only the date is asked; where no price was ever approved, the visit's current client rate is asked alongside the date.
Following it on your side
The Rate Approval column shows where every visit stands: Awaiting client, Approved, Declined, or Re-priced since. That last one earns its place — an approval is a sign-off on a specific amount, so if the price moved again afterwards the old yes doesn't cover it. Where a date was part of the ask, a second chip sits alongside reading Date pending, Date OK, or Date declined.
On the site visit itself the Client Rates card carries the same badge, with a matching one for the date. Hover either for who answered, when, and what they agreed to. A visit you never asked about shows nothing at all — an unasked price isn't "unapproved", it was simply never in question.
Three housekeeping rules keep that list honest, so your customer is never asked about something that no longer matters:
Finished visits are never re-asked. A visit that's completed, billed, cancelled, or whose dates are already past cannot drift into a new question for your customer, and any automatic re-ask still open on such a visit is withdrawn.
A changed price or date is only re-asked while a technician is engaged — one assigned, or one who has applied or countered. If you change the price on a visit nobody is currently in contention for, your customer isn't bothered; the moment a technician does request it, the change is re-checked and asked then.
Removing a technician withdraws every pending ask on that visit automatically, and a date approval tied to that technician's schedule is set aside. The visit's activity records that it happened.
One thing to be clear about: approving does not assign anybody, and nothing in the software stops you assigning a technician before the answer comes back. The shared page tells your external customer that we won't schedule until they've approved, so honouring that is on you.
When you still need their work order
A visit created before the client's real work order arrived carries a Service Envoy ticket number beginning SE — a placeholder. Once a technician is assigned to such a visit, the shared page's Work order paperwork needed chip counts it, and picking the chip shows a note reading Waiting on your work order for N site visits above just those visits.
It asks for the document rather than a number, because importing the work order is what generates the number — asking for the number sends them looking for something they'd get from you. And it lists only visits that already have a technician on them: an approved price on a visit nobody has picked up yet isn't a commitment to a person or a date, and asking for paperwork that early spends the only thing the list has going for it, which is being worth reading. The note tells them a technician is assigned and we're still working from our own reference number, so sending the work order over is what puts their number on the visit.
Once your external customer has approved a visit's times, the approve option goes away — there's nothing left to approve. They can still raise a dispute afterwards if they spot something later, which is deliberate: while the money is still on the platform we can act on it.
Nothing your external customer submits changes your records. They can say what they believe the times were, but only you can alter what's on the site visit. The technician is told their hours are being reviewed and can add their own account of the visit, but never sees your external customer's identity or what they wrote unless you choose to pass it along.
Taking sites back out of the project
Under Edit, what the remove option does depends on what you've selected, and it relabels itself to match.
Select only staged rows and it reads Delete. Those rows are deleted outright — a staged row is the whole record, so there's nothing else to keep.
Select rows that have already generated a ticket and it reads Remove from Project, with a confirmation listing exactly which tickets are affected. The ticket and its site visit are kept, along with any files and tasks on them — they simply stop belonging to this project. Use it to clear a duplicate row. If the ticket itself is the duplicate, delete it from the ticket page afterwards.
Delete Tickets Entirely is the harder one, kept apart because only it destroys the ticket — use it for sites your customer has taken back. It deletes the ticket with its site visits, tasks, files and rates, its listing in Available Work and every routing invite, any technician requests and counter-offers, and the project row. Before you confirm, each selected ticket is checked: one that has been invoiced, where a technician has checked in, or that is inside its arrival window can't be deleted and stays exactly as it is; one with a technician assigned who hasn't checked in is deleted anyway, but it's named first along with any late-cancel fee. The technicians involved are emailed (you can add a short message), any escrowed funding comes back to your balance, and you type DELETE to confirm. It can't be undone, and it needs a role that can delete tickets.