Your browser does not support JavaScript! Switch Connect API — pull your labor data from Switch

Switch Connect

Pull your labor data straight from Switch

A read-only API that returns the shift-level labor Switch staffed in your buildings — one facility or your whole portfolio, for the dates you ask for, on whatever schedule your team runs.

Version v1 Status Live Updated August 2026 For Client data & IT teams

What Switch Connect is

Switch Connect is a read-only API. Your team makes the call, Switch returns the labor records for the facilities and dates you asked for. Nothing is scheduled and nothing lands in an inbox — you pull what you need, when you need it.

Before this, moving Switch labor data into your systems meant a file on a schedule and someone to reconcile it. Switch Connect removes the file. The same data goes directly into payroll, PPD and PBJ reporting, your BI stack, or the dashboard your team already runs.

Two things shape every request:

  • Your organization is identified by a slug. Switch assigns it, and it sits in every URL. Your data is scoped to it — you can only reach the facilities mapped to your account.
  • Facilities are identified by Switch’s facility ID, not by your own building codes. Switch provides the mapped list.

Before your first call

Switch issues four things. If any of them are missing, open a support ticket and we’ll get them to you before you write a line of code.

What Switch provides
ItemWhat it is
slug A short identifier for your organization that appears in every URL — for example, example-health.
username + secret Your API credentials, issued by Switch and delivered by a one-time secure link that expires once it’s opened. Move the secret into your secrets manager as soon as it arrives — Switch can’t show it to you a second time and will reissue instead. Never sent in an email body, never posted on a page.
facility IDs Switch’s internal ID for each of your buildings, delivered as a mapping sheet alongside your own facility names and codes. Keep it — every per-facility call uses these numbers.
response format JSON or CSV. This is set per organization rather than per request, so every call against your slug returns the same format. Switch confirms which one you’re on.

Where your own details live

Everything on this page is identical for every Switch Connect client. The parts specific to you — your slug, your response format, and your facility mapping — sit on a private setup page that Switch issues you, behind its own password.

Your credentials aren’t on that page either. They arrive by one-time link, so the secret never rests in a web page or an email body.

The endpoint

Everything runs off one base URL:

https://api-v2.switchcare.com/v1/integrations/{slug}/labor-export
Placeholders
PlaceholderReplace with
{slug} Your assigned integration slug — for example, example-health.
{facility_id} Switch’s internal facility ID, from the mapping sheet — for example, 1042.
YYYY-MM-DD A calendar date in the facility’s local time — for example, 2026-05-02.

Authentication

Switch Connect uses HTTP Basic authentication over TLS. Send the username and secret Switch issued you on every request — there is no token to fetch and no session to refresh.

curl -u "{username}:{secret}" \
  "https://api-v2.switchcare.com/v1/integrations/{slug}/labor-export?date=YYYY-MM-DD"

Most HTTP clients take the pair directly; anything that doesn’t will accept the equivalent header, Authorization: Basic <base64 of username:secret>. Send it over HTTPS only — Basic credentials are encoded, not encrypted.

The four calls

You’re making two choices on every request: how many facilities and how many days. That gives you four URLs. Placeholders are marked in coral.

One facility per request

Add the /facility/{facility_id} segment. Use this when a single building’s data feeds a building-level system, or when you want to isolate one facility while testing.

Single date
https://api-v2.switchcare.com/v1/integrations/{slug}/labor-export/facility/{facility_id}?date=YYYY-MM-DD
Date range
https://api-v2.switchcare.com/v1/integrations/{slug}/labor-export/facility/{facility_id}?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD

All mapped facilities in one request

Drop the /facility/… segment entirely. One call returns every facility mapped to your slug — this is the one most teams run on a schedule.

Single date
https://api-v2.switchcare.com/v1/integrations/{slug}/labor-export?date=YYYY-MM-DD
Date range
https://api-v2.switchcare.com/v1/integrations/{slug}/labor-export?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD

Parameters

Parameter Where Format Notes
slug Path string Required on every call. Assigned by Switch.
facility_id Path integer Optional. Include the /facility/{facility_id} segment for one facility; omit it for all mapped facilities.
date Query YYYY-MM-DD A single calendar day, matched against each shift’s clock-in date. Do not send alongside start_date or end_date.
start_date Query YYYY-MM-DD First day of the window, inclusive. Matched against clock-in date. Must be paired with end_date.
end_date Query YYYY-MM-DD Last day of the window, inclusive — a shift starting on this date is returned with its full hours, overnight carry-over included. Must be paired with start_date.

Rules that matter

Five behaviors account for nearly every surprise teams hit in the first week.

  • One date mode per call. Send either date, or start_date and end_date together. Sending both is rejected.
  • A date window spans at most 90 days. Anything wider returns 400. For longer history, split the request into consecutive windows — see How teams use it.
  • Dates are local to the facility. A date is the calendar date in that building’s own timezone, not UTC and not your head office’s timezone. For a multi-state portfolio, that means one date call covers each building’s own local day.
  • A shift belongs to the date it started, and boundaries are inclusive. Dates filter on clock-in. A shift’s full hours are attributed to its start date, including any that carry past midnight — so an overnight shift clocking in at 11pm on the 3rd counts entirely on the 3rd, with nothing landing on the 4th. A range ending on the 3rd includes that shift and all of its hours.
  • An empty result is not an error. A facility and date with no shifts returns a successful, empty response — a JSON envelope with shifts: [], or a CSV with only its header row. It is never a 404. Build your loader to treat empty as “nothing happened that day,” not as a failure.
  • Format is fixed per organization. JSON or CSV is set against your slug. There is no format switch on the request, so you can hard-code your parser.

A worked example

One facility, a range that clears the cap:

https://api-v2.switchcare.com/v1/integrations/example-health/labor-export/facility/1042?start_date=2026-05-02&end_date=2026-07-03
Reading that URL
example-healthThe organization’s slug
1042One facility, by Switch’s facility ID
2026-05-02 → 2026-07-03Every shift clocking in from May 2 through July 3, both days included — comfortably inside the 90-day cap

Drop /facility/1042 from that URL and the same date window returns every facility mapped to example-health.

What comes back

A response is an envelope describing what you asked for, wrapping a shifts array. Facility and date sit on the envelope; each object in the array is one worked shift.

Envelope
FieldWhat it holds
facility_idSwitch’s facility ID for the building this response covers.
facility_nameThat building’s name.
dateThe date the response covers.
generated_atUTC timestamp of when Switch built the response. Worth storing alongside the load — it’s what tells you which pull a given row came from.
shiftsArray of shift objects. Empty when nothing was worked.
Shift object
FieldTypeWhat it holds
shift_idintegerIdentifies the shift. Stable across re-pulls, but not unique within a response — a shift paid under more than one code returns a row per segment, all carrying this ID. See One shift, more than one row.
employee_idintegerThe provider’s permanent Switch ID. It doesn’t change between quarters or across buildings, so one person carries one ID everywhere they work for you.
employee_first_namestringProvider’s first name.
employee_last_namestringProvider’s last name.
job_codestring · nullableShort role code, e.g. CNA. Can be null — see below.
job_descstring · nullableFull role name, e.g. Certified Nursing Assistant. Null whenever job_code is.
work_dateYYYY-MM-DDThe shift’s clock-in date in the facility’s local time. This is what your date filter matches and the day the whole shift is attributed to.
clock_inISO 8601Full timestamp with UTC offset, e.g. 2026-08-17T14:00:00-05:00. The offset is the facility’s, so local time is readable without a timezone lookup.
clock_outISO 8601Same format. On an overnight shift this carries the following calendar date, while work_date stays on the start date.
break_minutesintegerUnpaid break, in whole minutes.
total_hours_workeddecimalPaid hours, net of the break — see below.
pay_codestringWhich code the row’s hours were billed under — regular, weekend, overtime, holiday. Together with shift_id it identifies the row.
bill_ratenumberHourly rate billed for the shift.

A real response

One facility, one date, two of its shifts:

{
  "facility_id": "1042",
  "facility_name": "Example Care Center",
  "date": "2026-08-17",
  "generated_at": "2026-08-18T02:01:36.864Z",
  "shifts": [
    {
      "shift_id": 10001,
      "employee_id": 4021,
      "employee_first_name": "Dana",
      "employee_last_name": "Whitfield",
      "job_code": "CNA",
      "job_desc": "Certified Nursing Assistant",
      "work_date": "2026-08-17",
      "clock_in": "2026-08-17T06:00:00-05:00",
      "clock_out": "2026-08-17T13:30:00-05:00",
      "break_minutes": 30,
      "total_hours_worked": 7,
      "pay_code": "R",
      "bill_rate": 24
    },
    {
      "shift_id": 10002,
      "employee_id": 4088,
      "employee_first_name": "Marcus",
      "employee_last_name": "Ellery",
      "job_code": null,                          // unclassified — see below
      "job_desc": null,
      "work_date": "2026-08-17",
      "clock_in": "2026-08-17T14:00:00-05:00",
      "clock_out": "2026-08-17T22:00:00-05:00",
      "break_minutes": 14,
      "total_hours_worked": 7.77,
      "pay_code": "R",
      "bill_rate": 24
    }
  ]
}

When nothing was worked, the envelope still comes back with an empty array:

{ "facility_id": "1042", "facility_name": "Example Care Center", "date": "2026-08-17",
  "generated_at": "2026-08-18T02:01:36.864Z", "shifts": [] }

One shift, more than one row

A response is one row per shift and pay code — not one row per shift. When a single shift is billed under more than one code, each segment comes back as its own object, and they all carry the same shift_id.

An overnight that crosses into a weekend is the usual cause, but anything that changes the code mid-shift does it — hours tipping into overtime, a shift running into a holiday.

{
  
  "shifts": [
    {
      "shift_id": 10003,          // same shift
      "work_date": "2026-08-17",
      "clock_in": "2026-08-17T19:00:00-05:00",
      "clock_out": "2026-08-18T07:30:00-05:00",
      "break_minutes": 30,
      "total_hours_worked": 5,    // hours billed under this code
      "pay_code": "R"
    },
    {
      "shift_id": 10003,          // same shift, second segment
      "work_date": "2026-08-17",
      "clock_in": "2026-08-17T19:00:00-05:00",
      "clock_out": "2026-08-18T07:30:00-05:00",
      "break_minutes": 30,
      "total_hours_worked": 7,
      "pay_code": "RW"
    }
  ]
}

This is not duplication, and it is not a conflict. The two rows describe different parts of one shift.

Everything except total_hours_worked and pay_code repeats on every segment, because those fields describe the whole shift rather than the slice. The clock times are the shift’s, and so is break_minutes — it is the one break, reported on each row, not a break per segment.

Two ways this corrupts a load, both silent

  • Keying on shift_id alone. The second segment overwrites the first, its hours vanish from your totals, and nothing errors. Key on shift_id and pay_code together.
  • Summing break_minutes across rows. A two-segment shift then shows a 60-minute break where 30 were taken. Take the break once per shift_id, not once per row.

How hours are calculated

total_hours_worked is the clocked span minus the break, rounded to two decimals:

(clock_out − clock_in) − break_minutes = total_hours_worked

06:00 → 13:30  =  450 min  −  30  =  420 min  =  7.00
14:00 → 22:00  =  480 min  −  14  =  466 min  =  7.77

When a shift is split across pay codes, that same figure is the sum of its segments:

19:00 → 07:30  =  750 min  −  30  =  720 min  =  12.00

   regular   5.00
   weekend   7.00
             -----
             12.00   ← matches the net clocked span

So a shift’s true hours are the total across every row sharing its shift_id, and that total reconciles against the clock. It is the check worth building into your load.

Do not subtract break_minutes again. It is reported so you can show or audit it, not so you can apply it — the break is already out of the total. Double-subtracting understates every shift you report, and it fails silently.

Two things that will bite a naive loader

  • job_code and job_desc can be null. A shift with no role attached still carries real hours. Decide up front whether those hours are excluded, bucketed as unclassified, or held for review — don’t let them fall through a lookup and vanish.
  • One row is not one employee-day. The same employee_id can appear several times on one date — separate shifts, or separate pay-code segments of one shift.

Counting hours across a boundary

Because work_date is the clock-in date, consecutive windows never double-count and never drop an overnight shift. A window ending March 31 and the next one starting April 1 partition the data cleanly, even where a shift ran through midnight on the 31st — those hours belong to March in full.

CSV

If your slug is set to CSV you get the same shift fields flattened to a header row plus one row per shift, with the envelope’s facility and date repeated on each row.

Still to confirm with Switch

  • How the all-facilities call reports facility. On a single-facility response the facility sits on the envelope and not on each shift. Confirm what comes back when you drop the /facility/… segment, and how each shift is attributed to its building.
  • What date holds on a range request, given the envelope carries a single date on a single-date call.
  • The full pay_code set, beyond R.
  • Whether job_code values map straight to the codes your downstream system expects, or need a lookup on your side.
  • The exact CSV header and column order, if you are on CSV.

Status codes

CodeMeaningWhat causes it
200 Success The request was valid. The response may legitimately contain zero records — see Rules that matter.
400 Bad request A date window wider than 90 days; date sent together with start_date/end_date; or a date that isn’t formatted YYYY-MM-DD.
Authentication failure Missing or incorrect credentials. Exact code to be confirmed by Switch.
Unknown slug, or a facility not mapped to you A facility ID outside your mapped list, or a slug that doesn’t exist. Exact code to be confirmed by Switch.

If a call fails and the cause isn’t obvious from the list above, open a support ticket with the full URL and the timestamp — secret removed — and we can trace it.

How teams use it

A daily pull

One call, all facilities, yesterday’s date. It’s a single request for the whole portfolio and it keeps your load idempotent — re-running the same day overwrites cleanly rather than appending.

https://api-v2.switchcare.com/v1/integrations/{slug}/labor-export?date=YYYY-MM-DD   # yesterday, per facility-local date

Backfilling history

Because a window caps at 90 days, a year of history is a loop of consecutive windows rather than one large request. Chunk at 60 days rather than 90 — it leaves room to shift a boundary without recalculating the whole sequence.

# a year, in 60-day windows
?start_date=2025-09-01&end_date=2025-10-30
?start_date=2025-10-31&end_date=2025-12-29
?start_date=2025-12-30&end_date=2026-02-27
…and so on

Reconciling a single building

When one facility’s numbers look wrong, pull that facility on its own for the period in question. It’s a much smaller payload to eyeball against your own records than a portfolio-wide response.

Late adjustments — confirm with Switch

Timecards are sometimes corrected after a shift closes. Ask Switch how a corrected record behaves when you re-pull a date you’ve already loaded, and set your reconciliation window accordingly.

Getting help

Anything the pages above don’t answer — a call returning an error you can’t place, a facility missing from your mapping, a credential that needs reissuing — goes through a support ticket. It’s tracked, and it doesn’t depend on one person being at their desk.

Switch Connect

Open a support ticket

Tell us what you were calling and what came back. We'll pick it up from there.

What to include

For a failing call, the full request URL and the timestamp are usually enough to trace it — with the secret removed. Never paste credentials into a ticket, an email, or a screenshot. If you already have, say so in the ticket and Switch will rotate them.