Skip to content

OAuth (account access)

Use OAuth when you’re building an integration that an SMBcrm user authorizes to act on their account/location. After the user approves, you exchange an authorization code for a short-lived access token and a refresh token you use to get new access tokens.

  1. Send the user to the installation URL SMBcrm gives you for your integration. Use it exactly as given: it already carries your client_id, your redirect_uri and the scopes your integration uses.
  2. The URL opens a sign-in and approval page on marketplace.leadconnectorhq.com, which shows the LeadConnector name. That’s expected. The user signs in with their SMBcrm email and password, chooses the account/location to authorize, and approves. The browser then returns to your redirect_uri with a one-time code in the query string, for example https://your-app.example.com/oauth/callback?code=<authorization_code>. Exchange it right away.
  3. Your server exchanges the code for an access_token and refresh_token.
  4. You call the API with Authorization: Bearer <access_token>, refreshing when it expires.

Do this on your server. The client_secret must never reach the browser. Set user_type=Location to receive a token scoped to a single account/location. Have the user authorize from inside their own account/location, then check the response: a Location token has "userType": "Location" and a locationId.

Terminal window
curl -X POST https://services.smbcrm.com/oauth/token \
-H "Accept: application/json" \
-H "Version: v3" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=<client_id>" \
--data-urlencode "client_secret=<client_secret>" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=<authorization_code>" \
--data-urlencode "user_type=Location" \
--data-urlencode "redirect_uri=https://your-app.example.com/oauth/callback"
200 OK
{
"access_token": "<access_token>",
"token_type": "Bearer",
"expires_in": 86399,
"refresh_token": "<refresh_token>",
"scope": "contacts.readonly contacts.write",
"locationId": "<location_id>",
"companyId": "<company_id>",
"userId": "<user_id>",
"isBulkInstallation": false,
"userType": "Location"
}

expires_in is in seconds, and an access token lasts about 24 hours. A refresh token is valid for up to a year. Each time you use it you get a new one, valid for another year. scope is a space-separated list of the scopes granted, and userId is the person who authorized the app.

Store the refresh_token on your server and note the returned locationId, which is the account/location this token acts on.

Access tokens expire after about 24 hours. Use the refresh_token to get a new one without sending the user back through authorization.

Terminal window
curl -X POST https://services.smbcrm.com/oauth/token \
-H "Accept: application/json" \
-H "Version: v3" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=<client_id>" \
--data-urlencode "client_secret=<client_secret>" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "refresh_token=<refresh_token>" \
--data-urlencode "user_type=Location"

The response has the same shape as above. It always includes a new refresh_token, and the refresh token you just used stops working. Save the new refresh_token and access_token before you make any other call, and refresh from a single place so two requests never use the same refresh token.

To handle expiry, call the API with the stored access token. If the call returns 401 because the token expired, refresh, store both new tokens, and retry the original call once. Put this in one helper function so every request goes through it. You can also refresh ahead of time using expires_in.

If the refresh token goes unused for a year, or you lose it, the user has to authorize again.

The token endpoint returns two error shapes, so parse both: { "error", "error_description" } and { "statusCode", "message" }. On a 422, message is an array of strings.

401 Unauthorized
{
"error": "UnAuthorized!",
"error_description": "Authorization code not found"
}
400 Bad Request
{
"statusCode": 400,
"message": "Location is not active"
}
Status Cause What to do
400 The body is not form-encoded (invalid_request), a parameter is missing or has an invalid value, or the refresh token is invalid, expired or already used. Fix the request. If the refresh token is the problem, send the user through authorization again.
400 Location is not active: the account is paused or inactive. Try again later with the same refresh token. It works again once the account is active and the app is still installed.
401 The authorization code was not found (it is wrong or was already used), or the refresh token is invalid. Send the user through authorization again.
422 A field name is wrong, such as camelCase clientId, or a required field is empty. Use the snake_case names listed above.

Treat a 400 or 401 on a refresh as a signal to re-authorize, except for Location is not active.

Terminal window
curl https://services.smbcrm.com/contacts/<contact_id> \
-H "Authorization: Bearer <access_token>" \
-H "Version: v3"

Request only the scopes you need, and keep every secret on your server. See Token Safety.