Routes & runs API
Send your routes, stops, timetables, daily runs and passengers to Yipii from your own system, with one JSON call per batch, and get webhooks back when passengers use their links.
If your routes live in another system, such as a school-transport planner, TransportPlus or a spreadsheet your office keeps, you can send them to Yipii instead of typing them in. What you send becomes the same routes, runs and passengers your team sees under Sharing → Routes. Passengers then get a live link to their bus.
There are three ways in, and they all lead to the same place:
| Way | Best for |
|---|---|
| Settings → Import (a CSV, an Excel file or a Google Sheet) | A one-off load, or an office that keeps a sheet. A connected Google Sheet is checked every 30 minutes. |
| The rows API (this page) | Your system pushes changes as they happen. |
POST /routes/sync (older) | Only moving a route number onto a different vehicle. |
Before you start
You need:
- An API key. Create one in Settings → API Access (see API Keys).
The key acts as the person who created it. That person must be an admin, fleet
manager or office staff in the account, or every import call answers
403. - Your account key. This is the short name in your dashboard's address, such as
onpointiniot.yipii.io/onpoint/….
Imports go to the integrations service:
Every request carries Authorization: Bearer YOUR_API_KEY.
Send a route with its stops
A route is identified by its route number. Send its stops nested inside it, in the order the vehicle reaches them:
The answer comes back once every row has been written:
rows counts stops, because each stop is one row, just as it would be in a
spreadsheet. created_ids holds the ids of the routes that are new.
Send the same route again and it is updated, not duplicated. Stops are edited in place, so passengers already booked on a stop stay on it. Sending a renamed first stop renames that stop. It does not create a new one.
Route fields
| Field | Required | Notes |
|---|---|---|
route_number | yes | Your identifier. It matches the route on every later call. |
route_name | Shown to passengers. | |
vehicle | A plate or vehicle name already in your account. Vehicles are matched, never created. | |
driver | The name, email or phone of someone in your account. | |
days | Mon-Fri, Weekdays, Mon, Wed & Fri, Tuesday to Thursday, Daily, Weekends. | |
start_time, end_time | 07:00, 7.15, 3:30 pm. | |
effective_from | The date the route starts. | |
stops[] | Each stop takes name, address, lat, lng, time and kind. | |
stops[].time | A clock time (07:20) or minutes from the start (15). | |
stops[].kind | pick-up or drop-off. Leave it out for a stop that is both. |
Send the day's runs
A run is one day's trip on a route. A route always has its timetable; send runs only for what is different on a given day, such as a notice, another vehicle, another driver or a cancellation:
There is one run per route per date. Sending the same route and date again updates
that run, which is also how you cancel one. Dates in the past are skipped. Dates may be
written 2026-10-06 or 06/10/2026.
| Field | Required | Notes |
|---|---|---|
route_number | yes | |
date | yes | |
start_time, end_time | Defaults to the route's own times. | |
vehicle, driver | For this day only. | |
notice | Shown to passengers on the run's link. | |
status | scheduled (default) or cancelled. |
Send passengers
Stops are matched by name, ignoring capitals, against the route's stops. A passenger is
matched by external_id first, then by name on that route, so a second call updates the
passenger it matches.
An import never messages anyone. To send the new passengers their links, pass the
job's created_ids to:
The answer counts who was sent a link and who was not, and why:
{"counts": {"sent": 2, "not_approved": 0, "opted_out": 0, "no_channel": 1}}.
Large batches
Up to 500 rows, counting each stop as a row, are written before the call answers (200). From 501 to 5,000 rows the
call answers 202 straight away with status: "pending". Read the result from:
Poll it until status is done or failed. Each key may make 30 row calls a minute.
When something is wrong
| Answer | Meaning |
|---|---|
404 | The account key is not one this API key belongs to. |
403 | The key's person is not an admin, fleet manager or office staff. |
422 | The whole call was refused. The most common cause is a misspelt field, such as Unknown field route_numbr. Nothing was imported. |
200 with entries in errors | Some rows were left out. Each entry gives the row (its place in your call, counting from 1, with each stop counting as a row) and the reason, such as No Route number or No route S99 — import the route first. |
To see every import and its fields as the server has them, call
GET /api/v1/{account_key}/imports/targets.
Webhooks
Yipii can call your server when something happens on a passenger's link. Register an address with:
The answer includes a secret once. Save it, because it cannot be read again.
| Event | Sent when |
|---|---|
viewer_joined | Somebody opens a link. |
access_requested | Somebody asks for access to a private link. |
access_approved | Your team approves a request. |
form_submitted | A passenger fills in a link's form. |
Each call is a POST of JSON with two headers:
X-Yipii-Eventnames the event.X-Yipii-Signatureissha256=followed by the HMAC-SHA256 of the raw body, signed with your secret.
Check the signature against the raw body before you parse it:
Answer with any 2xx within 5 seconds. A delivery is attempted once and is not
retried. Every attempt is logged with its response code at
GET /api/{account_key}/public-tracking/webhooks/{id}/deliveries. To check your endpoint,
call POST …/webhooks/{id}/test, which sends a test_ping event.
Webhooks for runs, such as a run starting, a vehicle reaching a stop or a run finishing,
are not available yet. Until they are, read a route's runs from
GET /api/{account_key}/public-tracking/routes/{id}/occurrences.
Was this page helpful?