Our API uses OAuth 2.0 for authentication, specifically the client credentials grant type.
The authentication process consists of requesting an access token with your organization’s
API key and API secret, and using this access token in the Authorization HTTP header
of any subsequent requests.
The header is Authorization. A request that omits it, or uses a
different header name, is rejected with a 401.
Two grant types are relevant. client_credentials issues an
app token and is what an API key/secret pair gives you — this is what almost all integrations use.
password issues a user token tied to one individual.
A few operations accept only one or the other; see Access requirements below.
To generate API key/secret pairs, go to the System Settings page, click “Integrations”, and click “Generate new API credentials”. The credentials will be listed in the table on that page.
If you no longer use the API credentials or you suspect they have been compromised, please delete them, and generate new ones instead, if needed.
Receiving an access token:
curl -v https://api.crewsense.com/oauth/access_token \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_SECRET_KEY" \
-d "grant_type=client_credentials"
If the request is successful and your credentials are correct, you should receive a JSON response like this:
{
"access_token": "DZs3IeaMP5uEAc2I19kJYl8Tbvsmgq9GaPQPaMjN",
"token_type": "bearer",
"expires": 1426274440,
"expires_in": 86400
}
You use access tokens to authorize any requests made towards our API. To request an access token, issue a POST request to https://api.crewsense.com/oauth/access_token
The token_type signifies that you have to use HTTP headers to authorize requests (see the next section).
expires is a UNIX timestamp of the expiration date of the token (after which you have to request a new one).
expires_in shows the expiration length in seconds.
Token lifetime depends on the grant type you used:
| Grant | Lifetime | expires_in |
|---|---|---|
client_credentials — a standard API key | 24 hours | 86400 |
password | 30 days | 2592000 |
issued via refresh_token | 1 hour | 3600 |
If you try to use an expired access token, you receive:
{
"status": 401,
"error": "unauthorized",
"error_message": "Access token is not valid"
}
In this case, you simply have to request a new access token using the method described above.
Expired and invalid tokens return the same 401 message;
treat any 401 as “fetch a new token and retry once”.
Errors from /oauth/access_token use a different body from the rest of the API —
there is no status field, and the detail key is
error_description rather than error_message:
{
"error": "invalid_client",
"error_description": "Client authentication failed"
}
One thing worth knowing when you are debugging credentials: a wrong API key or secret returns
401 with invalid_client, but a wrong
username or password on the password grant returns
400 with invalid_request — the only way to tell that
apart from a malformed request is the error_description text. Requesting a grant type we
do not support returns 501.
curl example
curl -v https://api.crewsense.com/v1/company \
-H "Authorization: Bearer DZs3IeaMP5uEAc2I19kJYl8Tbvsmgq9GaPQPaMjN"
GET /v1/company takes no parameters and returns your organization's details, so it is a good first call to confirm your token works. To access protected resources in the API, you have to sign the HTTP requests with an Authorization header, using the access_token acquired in the previous step.
Authorization: Bearer DZs3IeaMP5uEAc2I19kJYl8Tbvsmgq9GaPQPaMjN
We use a simplified version of the ISO 8601 standard. Dates are represented in the YYYY-MM-DD format. Most dates with timestamps follow the YYYY-MM-DD hh:mm:ss format (e.g. “2015-03-15 19:33:59”), where the timestamp is “timezoneless”, it is implied to be in your organization’s timezone (or the timezone is irrelevant).
For a few timestamp type data fields, we use the (still ISO 8601 standard) YYYY-MM-DDThh:mm:ss+00:00 format (example: “2015-03-21T19:45:33-06:00”). This is used for fields like contact time, response time, creation date etc., where the timezone may be important (due to daylight savings time for example).
Most list endpoints (announcements, callbacks, trades, logs):
{
"data": [ items on the current page ],
"pagination": {
"prev": "https://api.crewsense.com/v1/[resource]/[offset]",
"next": "https://api.crewsense.com/v1/[resource]/[offset]"
}
}
Date-windowed endpoints (time_offs):
{
"items": [ items on the current page ],
"metadata": {
"links": {
"prev": "https://api.crewsense.com/v1/[resource]?start=...&end=...&after=...",
"next": "https://api.crewsense.com/v1/[resource]?start=...&end=...&after=..."
},
"start": "...", "end": "...", "page_start": "...", "page_end": "..."
}
}
Large result sets are split into pages. Two envelope shapes are in use, depending on the endpoint — check which one an endpoint returns from its own response schema.
Most list endpoints — including announcements, callbacks, trades and logs — return a data array alongside a pagination object holding prev and next page links. A link is a full URL while more pages exist and is empty at the ends. The empty value is an empty string "" on some endpoints and null on others, so treat any falsy value as “no more pages” rather than testing for one specific value.
Date-windowed endpoints — such as GET /time_offs — instead return an items array with a metadata object. metadata.links holds the prev/next URLs, and metadata also carries start, end, page_start and page_end dates (the originally requested window, and the window covered by the current page). A null link means you are at that end of the results.
Two things gate every request. Between them they account for most failed first integrations, and neither is visible from an endpoint’s own documentation.
1. Your organization must be on the Pro plan.Every /v1 endpoint checks this. If your organization is not on Pro, every call returns:
{
"error": "forbidden",
"status": 403,
"error_message": "The API is only available to Pro accounts."
}
If you see this on every endpoint, it is a plan issue rather than anything wrong with your request. Contact your Vector Scheduling representative.
2. Some endpoints require a specific token type.Most endpoints accept either an app token (client_credentials) or a user token
(password). A few accept only one. Notably
GET /payroll requires an app token — the type
a standard API key gives you. Sending the wrong type returns a 403 naming which is needed:
{
"error": "forbidden",
"status": 403,
"error_message": "This resource is only available with an app access token."
}
The endpoints documented here are the integration API and are all reachable with an
app token (a standard API key); GET /payroll
and GET /schedule require one specifically. There is a
corresponding message for resources that require a user token
("This resource is only available with a user access token."), but those are the
mobile-app resources and are not part of this reference — you will not encounter that message using the
endpoints documented here.
Most /v1 errors use this shape:
{
"error": "invalid_params",
"status": 422,
"error_message": ["The name field is required."]
}
Four things about it will affect how you write your error handling, so they are worth stating plainly:
error_message is sometimes a string and sometimes an array of strings.
Validation failures return an array — and may contain several messages from one request. Most other errors return a single string.
Treat it as string | string[].status duplicates the HTTP status code. You do not need to read it.error key for 404 is
"not found" — with a space, not an underscore.error is null for status codes that have no mapped key. In practice that means 429.The error values you can receive are
bad_request (400),
unauthorized (401),
forbidden (403),
not found (404),
conflict (409),
gone (410),
invalid_params (422),
internal_server_error (500).
Two endpoint families use a different shape entirely.
/oauth/access_token is covered under Receiving an access token above.
A small number of older write endpoints report validation failures as
200 with {"success": false, "errorMessages": [...]} —
where that is the case it is noted on the endpoint. For those, branch on the
success field rather than the status code.
A 401 means fetch a new token and retry once. A 403 is not
retryable — it is a plan, token-type, or ownership problem. A 422 means fix the request.
A 500 on a write should be followed by re-reading the resource before you retry:
a few operations complete their write and then fail while building the response.
Successful GET responses carry an ETag. If you poll an endpoint,
send the last ETag you received back as If-None-Match on your next
GET; when nothing has changed you get 304 Not Modified with an empty body.
curl -v https://api.crewsense.com/v1/users \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H 'If-None-Match: "8f14e45fceea167a5a36dedd4bea2543"'
GET. Conditional requests are ignored on write methods, and
do not use HEAD to fetch the validator — a HEAD
response has no body, so its ETag will never match the one from a GET
and your If-None-Match will never produce a 304. Always take the
ETag from the GET you are polling.ETag is present on successful (2xx) and
304 responses. Error responses (for example 404,
405, 500) do not carry one, so fall back to a normal
GET whenever the If-None-Match request does not return 304.If-Modified-Since is not supported. Use If-None-Match.ETag is a hash of the response body, so it changes whenever any byte of the payload changes.304 still executes the underlying query, so it saves response bandwidth rather than server work — please continue to poll at a considerate interval.One endpoint is rate limited today: Ready Alerts is capped at
30 messages per organization per rolling 24 hours. Exceeding it returns
429:
{
"error": null,
"status": 429,
"error_message": "You are limited to 30 messages in a 24 hour period. Please wait a while before sending the next one."
}
Note that this quota is shared with group notifications sent from inside the Vector Scheduling
application — messages your administrators send through the web interface count against the same 30.
Note also that error is null here rather than a key.
Current version: v1. The version is the first path segment, as in
https://api.crewsense.com/v1/users. It is the only version available to API-key integrations, and it is stable.
This page is the canonical reference. Older documentation you may have been linked to previously is retired and is
no longer maintained; anything it says that contradicts this page is wrong. Build against
https://api.crewsense.com/documentation/.
v1
Anything in that second list requires a new version prefix. v1 would continue to work.
Every change to this API is recorded in the Changelog below, newest first. If you maintain an integration, that section is the thing to watch. For questions, or to be told directly about changes affecting an integration you run, contact support.scheduling@vectorsolutions.com.
Documentation release. No API behavior was changed by any item below — these are corrections to what this page and the interactive reference said the API does.
Authorization header, not
Authentication as this page previously stated.client_credentials, 30 days for password, and 1 hour when issued via
refresh_token.200 was listed
anywhere. See the new Errors section for the response shape and for the cases where it differs./shifts/{id}/holdover/{date}, not /shifts/{id}/holdover.
Requests to the previously documented paths were being rejected. A previously undocumented operation for reading one date’s
holdover is now listed.developer.crewsense.com documentation. This page is now the single
canonical reference.GET /day_labels: query day header colors and labels by date range.BREAKING CHANGE Added pagination to the GET /time_offs endpoint, to allow large date periods to be queried efficiently.
GET /time_offs now includes length and real_length (length without break) in the response. Long time off entries are split up into their segments according to the underlying rotation of the user.GET /users/{user_id}/qualifiers and the following few endpoints.GET /titles, also available as GET /lists).accrual_start_date, accrual_profile and accrual_track in GET /users, GET /users/{id} and PATCH /users/{id}Batch processing of finalizations now available!
See POST /finalization and DELETE /finalization for details.
DELETE /time_offs/{id} endpointGET /scheduleSignup Board now available via API! Create new signup events, list signed up users, add users to the event and more.
10/31/2018{
"length": 24,
"break_start": "2018-10-31 12:00:00",
"break_end": "2018-10-31 12:45:00",
"break_length": 0.75,
}
New fields in /schedule#day.assignment.shifts
The following fields have been added to the GET /schedule endpoint, under shifts:
length - The full length of the shift in hours, including break periods.break_start - Start of the break period, if any. null otherwise.break_end - End of the break period, if any. null otherwise.break_length - Length of the break period, if any, in hours.