Skip to content
ShiftlyDevelopers
Documentation menu

Connect a business

At the end of this guide a business owner can connect your app from inside your product, and your app holds credentials for that business.

Before you start

  • Your client id and client secret.
  • A return address registered with your app. Shiftly only sends people back to an address on that list, matched exactly.
  • A PKCE implementation: a verifier of 43 to 128 characters from the unreserved set, and its S256 challenge.

1. Build the authorize request

GEThttps://host.shiftly.au/oauth/authorize

ParameterValue
response_typeAlways code.
client_idYour app's client id.
redirect_uriOne of your registered return addresses, exactly as registered.
scopeThe permissions you want, separated by spaces. Add offline_access to stay connected after the person signs out. See Permissions.
stateA value you generate per attempt and check on return, up to 512 characters. Protects the return against forgery.
code_challengeThe base64url SHA-256 of your verifier.
code_challenge_methodAlways S256. PKCE is required; a request without it is refused.
business_selectionOptional. multiple lets someone who manages several businesses tick each one to connect in one consent. Leave it out to connect one business. Send it only if your code stores every entry in connections (step 4).

Send the person's browser to that address. Shiftly checks the app and the return address first. If either is wrong, it does not send the person to your address at all; it shows them that the connection request has expired. For any other problem, such as a permission your app is not allowed to ask for, it sends them back to you with error and error_description.

2. What the owner sees

The person signs in to Shiftly if they are not already. Shiftly then shows a consent screen with your app's name, the business it will have access to (or, with business_selection=multiple, the businesses to tick), and two columns: what it will be able to view, and what it will be able to add or change. The screen also says what your app cannot do, and that staff dates of birth and addresses are shared when you ask for staff details or leave.

  • Someone who manages several businesses chooses one, or ticks several when you sent business_selection=multiple. Each business is its own connection, with its own credentials.
  • When ticking several, nothing starts ticked, and a business the person can't connect is named with the reason. If one ticked business can't be connected, or your app has no room for all of them, nothing is connected.
  • Only a business owner, or a manager who can manage team access, can connect an app. Anyone else is told to ask an owner.
  • A permission the person does not hold themselves is shown as one they can't share. The connection is created without it, and an owner can grant it later by connecting again.
  • If the business is already connected to your app, the button reads Update access and the new permissions replace the old.
  • While your app is in limited release and at its connection limit, a new business is told the app can't take new connections yet.

The consent screen itself is valid for 15 minutes from the authorize request. After that the person is told to start again from your product.

3. Handle the return

Allowed: the browser lands on your return address with code and state. Check that state matches what you sent, then exchange the code within five minutes.

Allowed
https://yourapp.example/shiftly/callback?code=xxxxxxxx&state=...

Cancelled: the person pressed Cancel, or Back to your app from a screen that could not proceed. You receive error=access_denied and your state. Nothing was shared.

Cancelled
https://yourapp.example/shiftly/callback?error=access_denied&state=...

4. Exchange the code

POSThttps://host.shiftly.au/oauth/token

Authenticate your app with HTTP Basic (client id as the username, secret as the password), or with client_id and client_secret in the JSON body. Send grant_type, code, the same redirect_uri and your code_verifier.

Response
{
  "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"
}

The scope in the response is what was actually granted, which can be less than you asked for. Store each refresh token against its connection_id. A code can be exchanged once; a second exchange is refused and every refresh token it led to is revoked, including ones already renewed, so never retry an exchange that returned a token.

Every code exchange also carries connections: one entry for every business that was connected, in name order, each with its own credentials and scope. It has one entry unless you sent business_selection=multiple and the person ticked several. The top-level fields repeat the first entry, so a client that reads only those still works for one business. A manager can share different things at different businesses, so read each entry's scope. A business that was disconnected between consent and exchange is left out.

Response, several businesses
{
  "access_token": "eyJhbGciOiJFUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 1800,
  "refresh_token": "shf_rt_...",
  "scope": "roster:read pay:read offline_access",
  "connection_id": "66f1c2a4b3d9e8f7a6b5c4d3",
  "business_id": "66f1c2a4b3d9e8f7a6b5c4d2",
  "connections": [
    {
      "access_token": "eyJhbGciOiJFUzI1NiIs...",
      "token_type": "Bearer",
      "expires_in": 1800,
      "refresh_token": "shf_rt_...",
      "scope": "roster:read pay:read offline_access",
      "connection_id": "66f1c2a4b3d9e8f7a6b5c4d3",
      "business_id": "66f1c2a4b3d9e8f7a6b5c4d2",
      "business_name": "Harbour Street Espresso"
    },
    {
      "access_token": "eyJhbGciOiJFUzI1NiIs...",
      "token_type": "Bearer",
      "expires_in": 1800,
      "refresh_token": "shf_rt_...",
      "scope": "roster:read offline_access",
      "connection_id": "66f1c2a4b3d9e8f7a6b5c4e7",
      "business_id": "66f1c2a4b3d9e8f7a6b5c4e6",
      "business_name": "The Wharf Hotel"
    }
  ]
}

What can go wrong

You seeWhyWhat to do
The owner lands on an expired-request page without reaching youThe client id is unknown, the app is suspended, or the return address is not registered exactly.Compare redirect_uri with the registered address character for character. Ask Shiftly to add an address.
error=invalid_scope on your return addressYou asked for a permission your app is not allowed to request, or none at all.Ask only for permissions on your app's allowed list.
error=invalid_request on your return addressPKCE was missing or not S256, or state was over 512 characters.Send code_challenge and code_challenge_method=S256.
invalid_grant from the token endpointThe code is older than five minutes, was already used, or the verifier does not match.Start the flow again.
invalid_client from the token endpointThe client id or secret is wrong, or the secret was rotated.Check the credentials; after a rotation, switch to the new secret before the overlap ends.