Authentication and authorization
Gusto Embedded supports two types of tokens used throughout development:
system-level
company-level
System access tokens allow you to carry out application-level actions such as:
Company access tokens allow you to carry out company-level actions, such as:
Each partner-managed company has a single access token that cannot be used to access other companies.
System access tokens
Starting with version v2024-04-01
, partner API tokens are deprecated in favor of system access tokens. These tokens improve security and allow you to:
- Manage multiple applications per organization
- Perform system-level actions like creating partner-managed companies and subscribing to webhooks
The token will expire after two hours. Unlike company access tokens, you can request additional system tokens anytime, even if a previous one is still active. Because of this, you donβt need to store the token unless you prefer to reuse it.
To get a system access token, send a POST request to the /oauth/token endpoint with grant_type
set to system_access
.
curl --location --request POST 'https://api.gusto-demo.com/oauth/token' \
--header 'Content-Type: application/json' \
--data-raw '{
"client_id": "{{client_id}}",
"client_secret": "{{client_secret}}",
"grant_type": "system_access"
}'
Hereβs what the response looks like:
{
"access_token": "PF9RH-QVnURJAY9-CHX0CC71HOPq7rClhJTdLdZOLt0",
"token_type": "Bearer",
"created_at": 1728518070,
"expires_in": 7200
}
To use the token, add it as a bearer token in the Authorization header:
Content-Type: application/json
Authorization: Bearer PF9RH-QVnURJAY9-CHX0CC71HOPq7rClhJTdLdZOLt0
Company access tokens
To call the API on behalf of a company, you need a company-level access token. You can get one by creating a partner-managed company or going through the in-app authorization code flow when migrating to embedded payroll.
When you create a partner-managed company, you'll receive:
Access_token
Refresh_token
expires_in
(how many seconds before the token expires)company_uuid
{
"access_token": "JKrGqRyrYfY1PB0YhsuRkbrrWBJ5iSUODDNA28D3yMc",
"refresh_token": "T0jHy4Oc3FWi7hDPEMPdpbGiLpB0rWeb1ZJOJVB36oU",
"company_uuid": "d525dd21-ba6e-482c-be15-c2c7237f1364",
"expires_in": 7200
}
Tokens use URL-safe base64 encoding.
Refreshing access tokens
Access tokens expire after two hours. If expired, API calls will return a 401 Unauthorized
error.
To refresh a token, send a POST request to the /oauth/token endpoint with:
client_id
andclient_secret
from your applicationrefresh_token
grant_type
set torefresh_token
curl --location --request POST 'https://api.gusto-demo.com/oauth/token' \
--header 'Content-Type: application/json' \
--data-raw '{
"client_id": "{{client_id}}",
"client_secret": "{{client_secret}}",
"refresh_token": "T0jHy4Oc3FWi7hDPEMPdpbGiLpB0rWeb1ZJOJVB36oU",
"grant_type": "refresh_token"
}'
Youβll receive a new access and refresh token:
{
"access_token": "737HdeXfIqgx-NfaUFRuhV7JDe6ns6ptanJSMuQzjlc",
"token_type": "bearer",
"expires_in": 7200,
"refresh_token": "iEjL96L9Pndwmi-xVX3Q-xbrvvhnjHYGX87sopgGJ8E"
}
When you use the new access token in a Gusto API call, your previous refresh token will be revoked.
Token management tips
Access and refresh token pairs are tied to a resource, typically a company. Hereβs an example structure for storing Gusto tokens:
company_uuid
: the Gusto company ID. You might link this to your own companies table.access_token_expiration
: based on theexpires_in
value. Since the default is 7200 seconds (2 hours), set your expiration check slightly earlier, for example, "expires_in
- 60."
To prevent token refresh race conditions, lock the row when refreshing tokens. Hereβs one way to handle refresh logic:
- Confirm the access token needs refreshing by checking if
access_token_expiration
is in the past or if a401
error was returned - Lock the row for the
company_uuid
- Refresh the token
- Update the access and refresh tokens and reset the expiration timestamp
- Unlock the row
- Use the new access token
- Make sure all processes use the most recent token
Updated about 1 hour ago