Quickstart
Connect the sandbox business, list its shifts and create a draft shift. About ten minutes.
Before you start
- Sandbox credentials from Shiftly: a client id, a client secret and the sandbox owner's sign-in. See Sandbox.
- A return address on your side that Shiftly can send the owner back to, registered with your app. It must start with https://.
- A way to run HTTP requests from your server or a terminal, not from a web page. The samples use curl.
1. Send the owner to Shiftly
Build a PKCE pair: a random verifier of 43 to 128 characters, and its challenge, the base64url SHA-256 of the verifier. Then send the owner's browser to the authorize address.
https://demo.shiftly.au/oauth/authorize
?response_type=code
&client_id=shf_your_client_id
&redirect_uri=https://yourapp.example/shiftly/callback
&scope=roster:read%20roster:write%20offline_access
&state=a-value-you-will-check-on-return
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256The owner signs in to Shiftly, sees your app's name and the permissions you asked for, chooses the business, and allows access. Shiftly sends them back to your return address with a code and your state.
https://yourapp.example/shiftly/callback?code=xxxxxxxx&state=a-value-you-will-check-on-return2. Exchange the code
Within five minutes, exchange the code for credentials. Authenticate your app with HTTP Basic using the client id and secret, and send the verifier that matches the challenge.
curl -X POST https://demo.shiftly.au/oauth/token \
-u "shf_your_client_id:shf_sk_your_client_secret" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "xxxxxxxx",
"redirect_uri": "https://yourapp.example/shiftly/callback",
"code_verifier": "THE_VERIFIER_YOU_GENERATED"
}'{
"access_token": "eyJhbGciOiJFUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "shf_rt_...",
"scope": "roster:read roster:write offline_access",
"connection_id": "66f1c2a4b3d9e8f7a6b5c4d3",
"business_id": "66f1c2a4b3d9e8f7a6b5c4d2"
}Store the refresh token securely against the business. The access credential lasts 30 minutes; Stay connected covers renewing it.
3. List shifts
Every request goes to the v1 base address with the access credential as a bearer token. Date ranges are calendar days in the location's timezone.
curl "https://demo.shiftly.au/v1/shifts?from=2026-10-05&to=2026-10-11" \
-H "Authorization: Bearer eyJhbGciOiJFUzI1NiIs..."{
"data": [
{
"id": "66f1c2a4b3d9e8f7a6b5c4e1",
"location_id": "66f1c2a4b3d9e8f7a6b5c4d9",
"position_id": "66f1c2a4b3d9e8f7a6b5c4da",
"employee_id": "66f1c2a4b3d9e8f7a6b5c4db",
"roster_id": "66f1c2a4b3d9e8f7a6b5c4dc",
"start_time": "2026-10-05T09:00:00+11:00",
"end_time": "2026-10-05T17:00:00+11:00",
"status": "published",
"breaks": [{ "offset_minutes": 240, "duration_minutes": 30, "paid": false }],
"notes": null,
"paid_hours": 7.5,
"created_by_app": null,
"archived": false,
"created_at": "2026-09-28T03:12:45Z",
"updated_at": "2026-10-01T22:04:10Z"
}
],
"has_more": false,
"next_cursor": null
}4. Create a draft shift
Send an Idempotency-Key so a retry never creates a second shift. Leave breaks out and Shiftly plans them from the award.
curl -X POST https://demo.shiftly.au/v1/shifts \
-H "Authorization: Bearer eyJhbGciOiJFUzI1NiIs..." \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-H "Content-Type: application/json" \
-d '{
"location_id": "66f1c2a4b3d9e8f7a6b5c4d9",
"position_id": "66f1c2a4b3d9e8f7a6b5c4da",
"employee_id": "66f1c2a4b3d9e8f7a6b5c4db",
"start_time": "2026-10-07T09:00:00+11:00",
"end_time": "2026-10-07T15:00:00+11:00"
}'The response is 201 with the shift. Its status is unpublished: it is a draft on the roster, and a manager publishes it in Shiftly. Open the sandbox business in Shiftly and you will see it on the roster.
What can go wrong
| Response | Why | What to do |
|---|---|---|
| 401 token_expired | The access credential has expired. | Renew it with the refresh token and retry. |
| 403 permission_denied | You did not ask for roster:write, or the person who connected the app cannot edit rosters. | Ask for the permission at consent, or have an owner connect the app. |
| 422 validation_failed | A field is wrong. The error names it. | Fix the field named in error.field. |
| 409 conflict | The person already has a shift at that time. | Pick another time, or change the existing shift. |
Next
Connect a business explains every parameter of the flow you just ran, what the owner sees, and what happens when they cancel.
