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.
The flow
Section titled “The flow”- Send the user to the installation URL SMBcrm gives you for your integration. Use it exactly as given: it already carries your
client_id, yourredirect_uriand the scopes your integration uses. - 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 yourredirect_uriwith a one-timecodein the query string, for examplehttps://your-app.example.com/oauth/callback?code=<authorization_code>. Exchange it right away. - Your server exchanges the
codefor anaccess_tokenandrefresh_token. - You call the API with
Authorization: Bearer <access_token>, refreshing when it expires.
Exchange the code for a token
Section titled “Exchange the code for a token”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.
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"const body = new URLSearchParams({ client_id: process.env.SMBCRM_CLIENT_ID, client_secret: process.env.SMBCRM_CLIENT_SECRET, grant_type: 'authorization_code', code: authorizationCode, user_type: 'Location', redirect_uri: 'https://your-app.example.com/oauth/callback',});
const res = await fetch('https://services.smbcrm.com/oauth/token', { method: 'POST', headers: { Accept: 'application/json', Version: 'v3', 'Content-Type': 'application/x-www-form-urlencoded', }, body,});const token = await res.json();{ "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.
Refresh the access token
Section titled “Refresh the access token”Access tokens expire after about 24 hours. Use the refresh_token to get a new one without sending the user back through authorization.
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.
Token endpoint errors
Section titled “Token endpoint errors”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.
{ "error": "UnAuthorized!", "error_description": "Authorization code not found"}{ "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.
Use the access token
Section titled “Use the access token”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.
