# Stream a ServiceM8 account

> Land a ServiceM8 account's jobs, quotes, invoice lines, payments, bookings, clients, staff time and forms in your warehouse, read with an API key, with nothing to approve.

A ServiceM8 stream lands a ServiceM8 account's jobs and quotes, the lines and payments behind each invoice, the schedule, clients, staff time, forms and assets in your warehouse, as often as every 15 minutes. After the first run, each run reads only what changed in ServiceM8 since the last.

ServiceM8 is a **published source**, like [Postgres](https://docs.sanda-os.com.au/streams/postgres) and [Bite](https://docs.sanda-os.com.au/streams/bite): sanda wrote the code that reads it and runs it as its own, so there is no connector for sam to write or for anyone to approve. You type an API key the account's Business Owner makes in ServiceM8, choose what to read, and set a schedule.

:::note
ServiceM8 is in the catalogue once it is switched on for your console. If **Choose a source** doesn't list it, ask sanda support.
:::

:::note
ServiceM8's terms don't allow its data, or anything made from it, to be sent to an advertising network, so pushes to Google Ads and Meta refuse these tables. That holds for a view or a table built from them too: sanda follows a push's selection back to the tables it reads.
:::

## Before you start

- **The Business Owner makes the key.** In ServiceM8, only the Business Owner can make an API key. It is under **Settings · API Keys** (the button may read **API Access**). Make an API key there, not a Plugin API key (one that starts `smk-`): those are ServiceM8's older kind, and its API doesn't accept them. sanda only reads, so Read Only access is enough. Give the key a name such as `sanda`, so you can tell it apart from other keys later.
- **One account at a time.** Each key reads one ServiceM8 account. A business with two accounts connects each one and makes a stream for each.
- **Only owners and admins** connect a key and make a stream on it in sanda, because sanda reads the account on the whole workspace's behalf.

## Connect an account

:::steps
1. **Open Pull.** Go to **Data · Streams**, open the **Pull** tab and press the **ServiceM8** card under **Choose a source**. The new pull's editor opens on **Connect a ServiceM8 account** when none is connected yet. Once an account is connected, **Connect an account** beside ServiceM8 under **Connected** adds another.
2. **Paste the API key** into **API key**. **Name** is optional: leave it empty and sanda names the account after the business.
3. **Press Check and connect.** sanda asks ServiceM8 which account the key belongs to before it keeps the key. The account is listed under ServiceM8 in **Connected**, named after the business (such as `ServiceM8 · Acme Plumbing`), with the last characters of the key. If you connected it from a new pull's editor, the account is chosen there.
:::

sanda keeps the key encrypted, only ever sends it to ServiceM8, and never shows it again, in the console or to sam. **Type it again** replaces the key (after you make a new one in ServiceM8, say), and every stream on the account carries on. A key for a different ServiceM8 account is refused there, because a stream keeps the account it reads. **Disconnect** forgets the key in sanda. To stop it working in ServiceM8 as well, revoke it under **Settings · API Keys**.

## Make a stream

:::steps
1. **Start it.** Press the **ServiceM8** card under **Choose a source**, or **New pull** and then **ServiceM8**.
2. **Choose when it runs.** **When it runs** comes first. A ServiceM8 stream runs at most every 15 minutes, so the faster choices are greyed out. Within your trading hours is a good start. See [when a stream runs](https://docs.sanda-os.com.au/streams/schedules).
3. **Name it.** Its tables are named after it: a stream called `sm8` lands jobs in `raw.sm8__jobs`.
4. **Choose the account**, and, if your workspace has more than one warehouse, the warehouse it lands in. Both are chosen once.
5. **Choose what it reads.** Everything is ticked. Untick what you don't need: each one you leave out saves requests from the account's daily allowance.
6. **Press Save pull.** Saving reads nothing yet: press **Run now**, or wait for its schedule.
:::

## What it reads

| Tick | Lands in | Each run reads |
|---|---|---|
| **Jobs** | `raw.<stream>__jobs` | The jobs that changed since the last run |
| **Job materials** | `raw.<stream>__job_materials` | The job materials that changed since the last run |
| **Job material bundles** | `raw.<stream>__job_material_bundles` | The job material bundles that changed since the last run |
| **Job activities** | `raw.<stream>__job_activities` | The job activities that changed since the last run |
| **Job payments** | `raw.<stream>__job_payments` | The job payments that changed since the last run |
| **Job allocations** | `raw.<stream>__job_allocations` | The job allocations that changed since the last run |
| **Job contacts** | `raw.<stream>__job_contacts` | The job contacts that changed since the last run |
| **Job queues** | `raw.<stream>__job_queues` | The job queues that changed since the last run |
| **Clients** | `raw.<stream>__clients` | The clients that changed since the last run |
| **Client contacts** | `raw.<stream>__client_contacts` | The client contacts that changed since the last run |
| **Staff** | `raw.<stream>__staff` | The staff that changed since the last run |
| **Staff clock events** | `raw.<stream>__staff_time_events` | The staff clock events that changed since the last run |
| **Materials and labour rates** | `raw.<stream>__materials` | The materials and labour rates that changed since the last run |
| **Job categories** | `raw.<stream>__categories` | The job categories that changed since the last run |
| **Badges** | `raw.<stream>__badges` | The badges that changed since the last run |
| **Forms** | `raw.<stream>__forms` | The forms that changed since the last run |
| **Form questions** | `raw.<stream>__form_fields` | The form questions that changed since the last run |
| **Form responses** | `raw.<stream>__form_responses` | The form responses that changed since the last run |
| **Assets** | `raw.<stream>__assets` | The assets that changed since the last run |
| **Customer feedback** | `raw.<stream>__feedback` | The customer feedback that changed since the last run |
| **Job admin time** | `raw.<stream>__job_admin_activities` | The last 60 days again, once a day, so late changes land |
| **Tax rates** | `raw.<stream>__tax_rates` | The whole list, every run |
| **Account** | `raw.<stream>__account` | The whole list, every run |

Jobs, job contacts, clients, client contacts and staff hold people's names, phone numbers, email addresses or home addresses, and the editor says so under each. Untick them if your warehouse shouldn't hold them.

### What changed since the last run

The first run reads each list whole. After that, a run asks ServiceM8 for every record changed after a date: the date of the newest change the last run saw, less one day. So each run reads a day or two of changes again. A record read twice is still one row, and one that hasn't changed costs nothing in your warehouse. Reading a little again means a change is never missed, whatever ServiceM8's clock did, including the hour that repeats when daylight saving ends.

### Jobs, quotes and invoices

A quote is a job whose `status` is `Quote`, and a work order one whose status is `Work Order`: ServiceM8 keeps them in one list, so they land in one table. ServiceM8 has no separate invoice record either: a job's invoice is its `invoice_date`, `invoice_sent` and `total_invoice_amount`, with its lines (labour included) in job materials and the money received in job payments. Each line and payment names its job in `job_uuid`.

### The schedule and staff time

Job activities are both the bookings on the schedule and the time staff recorded on a job, told apart by `activity_was_scheduled` and `activity_was_recorded`. Job allocations are jobs given to someone without a booked time. Staff clock events are each clock on, clock off and lunch break.

### Job admin time

ServiceM8 records no time of change for office time logged against a job, so a run can't ask for what changed. Instead, the first run reads all of it, and after that, once a day, the stream reads the last 60 days again. An entry corrected inside those days lands corrected, and one removed is marked `_deleted_at`. An entry older than that keeps what it last said.

### Tax rates and the account

Both are a handful of rows, read whole every run. The account is the ServiceM8 account itself: its `timezone_name` is the time zone ServiceM8's times are in (with one exception: a form response's `timestamp` is UTC), and its `currency` the currency of every amount.

### Left out

ServiceM8 also offers these, which a ServiceM8 stream doesn't read yet: job notes, tasks, checklists and job templates, attachments (such as the quote and invoice PDFs), suppliers, locations, allocation windows and security roles.

A member of staff's last known position (`lat`, `lng` and `geo_timestamp`) and the job they are travelling to (`navigating_to_job_uuid` and its times) never land.

## What lands in your warehouse

Every field becomes a column, and the whole record is kept in `_record`, as for every [pull](https://docs.sanda-os.com.au/streams/in#where-the-records-land). A few things are worth knowing when you model them:

- **Times are the account's own time, with no time zone.** ServiceM8 writes its times as the wall-clock time where the account is, so these columns are timestamps without a zone. The `account` table's `timezone_name` says which zone: in a view, `edit_date at time zone 'Australia/Sydney'` gives the instant. A time ServiceM8 never set, which it sends as `0000-00-00 00:00:00`, is empty in its column and kept in `_record`.
- **A form response's `timestamp` is UTC.** When a form was completed is the one time ServiceM8 writes in UTC, so that column is a timestamp with a time zone: it already holds the instant. In a view, `"timestamp" at time zone 'Australia/Sydney'` gives the time on the account's clock, to set beside its other times. A form response's `edit_date` is the account's own time, like every other table's.
- **Amounts are numbers.** ServiceM8 sends money and quantities as text. They land as numbers in `total_invoice_amount` on jobs, in `quantity`, `price`, `cost`, `displayed_amount` and `displayed_cost` on job materials, in `price` and `cost` on materials, in `amount` on job payments and in `amount` (a percentage) on tax rates. A job material's `price` and `cost` are without tax, and its `displayed_amount_is_tax_inclusive` says whether `displayed_amount` includes it.
- **Deleted records stay, with `active` 0.** Deleting something in ServiceM8 marks it inactive rather than removing it, and ServiceM8 still lists it, so its row keeps coming in with `active` 0. Leave those out in a view where you count jobs or revenue.
- **Ids are ServiceM8's uuids.** Every table is keyed by `uuid`, and a column ending in `_uuid` names a row of another table: `company_uuid` a client, `staff_uuid` a member of staff, `category_uuid` a job category.
- **Badges are uuids.** A job's or client's `badges` holds the uuids of its badges, each a row of badges.

## Change a stream

**Edit**, on the stream's page, changes everything but the account and the warehouse. Something you take off keeps its table, with what it landed. **Load everything again** on the stream's page reads every list whole on the next run. A large account's first read, or a load from the start, can take several runs, each carrying on from the last.

## Remove ServiceM8 data

ServiceM8's terms ask that its data be deleted when you stop using it, or when someone asks. To remove an account's data from sanda:

:::steps
1. **Delete each pull** on the account: **Delete pull**, at the foot of the stream's page. Its tables stay in your warehouse until you remove them.
2. **Disconnect the account** under ServiceM8 in **Connected**, and revoke the key in ServiceM8 under **Settings · API Keys**.
3. **Drop what you built from it.** Remove the views and modelled tables in [Modelling](https://docs.sanda-os.com.au/modelling) that read the stream's tables.
4. **Ask sanda support to drop the stream's tables** (`raw.<stream>__jobs` and the rest), naming the stream. Landing tables can't be dropped from the console.
:::

## When something goes wrong

| What you see | What to do |
|---|---|
| ServiceM8 refused that API key (`401`). Check it and type it again. | The key was mistyped, was revoked in ServiceM8, or is a Plugin API key (one that starts `smk-`), which ServiceM8's API doesn't accept. Copy it again from **Settings · API Keys**, or make a new API key there |
| ServiceM8 didn't say which account that API key is for, so nothing was saved. | ServiceM8's answer named no account. Try again later |
| That account is already connected as ServiceM8 · … | The account is in **Connected** already. Press **Type it again** on that row to replace its key |
| That API key is for …, not … | A stream keeps the account it reads. Connect the other account as its own, and make a stream on it |
| ServiceM8 stopped accepting the API key for … | The key was revoked or changed in ServiceM8. The stream waits, rather than failing again and again. Make a new key and press **Type it again** beside the account under **Connected** |
| ServiceM8's daily allowance for this account is spent, so the run stopped. The next run after ServiceM8 resets it carries on. | Nothing to do: the run kept what it landed. sanda asks ServiceM8 again 60 minutes later, with one request, and the first run after ServiceM8 resets the allowance carries on. If it happens often, run the stream less often or read less |
| … sanda waits until … (UTC) before asking ServiceM8 again, so nothing was read. | A run that started while the allowance rests. Nothing to do: it asked ServiceM8 nothing, and isn't a failure |
| ServiceM8 says this account isn't in good standing and has invoices to pay, so it answers nothing until they are paid. | The Business Owner settles the account's ServiceM8 invoices. The next run after that carries on |
| ServiceM8 didn't let the API key read …, so this run left it out. | Take that list off the stream with **Edit**, or ask the Business Owner for a key that can read it. The rest of the run carried on |
| A ServiceM8 stream runs at most every 15 minutes … | Choose a slower schedule |
| ServiceM8 asked sanda to slow down, or didn't answer | Nothing to do: the run waits as ServiceM8 asks, then carries on from the last page that landed |

A failed run counts toward the stream pausing itself, as any stream's does. A run that stopped for the daily allowance, or was skipped while it rests, doesn't. See [runs](https://docs.sanda-os.com.au/streams/runs).

## Limits

- ServiceM8 allows each account 180 requests a minute and 20,000 a day. A run makes at most 2 requests a second, which keeps it inside the minute's allowance, and a stream runs at most every 15 minutes, which keeps its everyday runs well inside the day's. A large account's first read, or **Load everything again**, can spend more of the day.
- Each request returns up to 1,000 records: ServiceM8 sets the page size.
- One run lands at most 20,000,000 records, so a large account's first read is several runs.

A ServiceM8 stream is billed like any pull: a charge for each run that does its work, and the rows it lands past what your edition includes. See [what streams cost](https://docs.sanda-os.com.au/streams#what-streams-cost).

:::links
- [Pull](https://docs.sanda-os.com.au/streams/in): Every way records come into your warehouse on a stream.
- [Stream graphs](https://docs.sanda-os.com.au/streams/graphs): Model what a stream lands, then send the result out, every time it lands.
- [Runs](https://docs.sanda-os.com.au/streams/runs): What each run did, and what to do when one fails.
:::
