Authorization Code Flow

Let an existing Remote company grant your integration permission to manage their data on their behalf.

Use the Authorization Code Flow when a company already exists on Remote and you need their permission to act on their behalf. A company admin signs in, reviews what you are asking for, and approves. You receive tokens scoped to that company.

This is different from the Client Credentials Flow, which authenticates your integration as itself. Client credentials cannot read or write another company's data — only an admin's approval can grant that.

If instead you create the company yourself, see Create a company with partner credentials.

ℹ️ Sandbox vs production. Every URL below uses gateway.remote-sandbox.com. For production, replace it with gateway.remote.com. Scope values are identical in both.

Before you begin

You need three things from Remote:

ItemNotes
CLIENT_IDIdentifies your integration. Safe to include in URLs.
CLIENT_SECRETNever expose this in client-side code or a browser.
A registered redirect URIYour callback URL, registered by Remote when your credentials are created.

Redirect URI matching is exact

Your redirect URI must match a registered value character for character. No wildcards, no prefix matching, no normalisation. These are all different values, and only one will work:

https://example.com/callback
https://example.com/callback/     (trailing slash)
http://example.com/callback       (scheme)
https://example.com:443/callback  (port)

If you need to add or change a redirect URI, ask Remote — you cannot self-serve it.

⚠️ Local development. Loopback URIs such as http://localhost:3000/callback cannot currently be registered for partner integrations. To test this flow you need a callback reachable over HTTPS.

Step 1 — Send the admin to the authorization URL

GET https://gateway.remote-sandbox.com/auth/oauth2/authorize
ParameterRequiredDescription
client_idYesYour CLIENT_ID, URL-encoded.
redirect_uriYesMust exactly match a registered redirect URI.
stateYesA random string of at least 8 characters, unique to this attempt.
scopeYesSpace-separated permissions. Request only the scopes you need — see Scopes. If omitted, your token is issued with full access.
https://gateway.remote-sandbox.com/auth/oauth2/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
  &state=c97b8fa15f7f8ba064b338779b8eecab
  &scope=employment%3Aread%20timeoff%3Aread

⚠️ URL-encode your client_id. Client IDs can contain characters that are not URL-safe. A plus sign in particular must be written as %2B in the query string, or it will be read as a space and your request will fail. In the Authorization header later on, use the raw unencoded value.

Use a fresh state on every attempt

state protects against cross-site request forgery: Remote echoes it back unchanged, and you should reject any callback whose state you did not generate.

It also identifies the attempt. If you reuse a state after the admin has already approved or denied it, you get already_approved or already_denied back and no new code. Generate a new random value each time.

Step 2 — The admin signs in and approves

Remote handles this. If the admin is not signed in they are prompted to sign in first, then shown a consent screen naming your integration and the access you requested.

The signed-in user must be an Owner or Admin of the company. Anyone else sees an error instead of a consent screen.

Step 3 — Handle the callback

On approval, Remote redirects the browser to your redirect_uri with code and state query parameters.

Before doing anything else, confirm state matches the value you generated.

The code is valid for 5 minutes and can be redeemed once. A second attempt with the same code fails with invalid_grant even if the five minutes have not elapsed.

Step 4 — Exchange the code for tokens

POST https://gateway.remote-sandbox.com/auth/oauth2/token

Authenticate with HTTP Basic using your client credentials. Send grant_type=authorization_code and code as form-encoded body parameters.

curl --request POST 'https://gateway.remote-sandbox.com/auth/oauth2/token' \
  --header 'Authorization: Basic BASE64_CLIENT_ID_AND_SECRET' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'code=YOUR_AUTHORIZATION_CODE'

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "84224550-dc8f-4153-a7cd-4f38c7ef90da",
  "expires_in": 3600,
  "token_type": "Bearer",
  "company_id": "3718b8ba-55d3-4fa6-ae45-91cd43b67997",
  "user_id": "e25c2e12-be43-4964-ac00-40ddfbd896c4"
}

Store the refresh token securely and keep the company_id — it identifies the company that just authorized you.

ℹ️ This is the step that links the company to your integration. The company becomes available to your integration when you redeem the code, not when the admin clicks approve. If you never exchange the code, nothing is linked.

Sending credentials in the body

Passing your credentials only in the request body is not supported. If you send client_id in the body in addition to the Authorization header, the two must match exactly, or the request fails with 401 invalid_client.

Step 5 — Call the API

Pass the access token as a bearer token. It acts on behalf of the approving admin, limited to the scopes you requested and to that single company.

curl 'https://gateway.remote-sandbox.com/v1/employments' \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Step 6 — Refresh the access token

Access tokens last 3600 seconds. Refresh before expiry — every 55 minutes is a safe pattern. There is no limit on how many valid access tokens you may hold at once.

Send grant_type=refresh_token and your refresh_token, with the same Basic authentication. See Refresh Token Flow.

Your refresh token does not change when you use it. The same value stays valid and is returned again, so treat it as a long-lived secret and store it as securely as your client secret. You do not need to persist a new one after each refresh.

Choosing scopes

The scope parameter controls what your access token can do. Request only the scopes you need.

Scopes are enforced per endpoint. A token holding employment:read can list employments but cannot create one, and cannot touch payroll or expenses at all. See Scopes for the values you can request and how they are evaluated.

⚠️ Omitting scope grants full access. If you leave scope out, your token is issued with all:write — permission to do everything the approving admin can do. Always request explicit scopes.

Deprecated: company.manage

The scope https://gateway.remote.com/company.manage is deprecated. It grants full read and write access to everything the admin can do. Do not use it for new integrations — request the specific scopes your integration needs instead.

Errors during authorization

If something goes wrong before a code is issued, Remote redirects with error and error_description instead of code. Your state is always echoed back.

errorWhat it meansWhat to do
invalid_requestA parameter is missing or invalid. This includes a redirect URI that is not registered.Check every parameter, especially that your redirect URI matches exactly.
unauthorized_clientYour client ID is unknown, or your integration is not enabled.Confirm your credentials with Remote.
invalid_scopeYou requested a scope the approving user cannot grant.Check Scopes.
access_deniedThe user declined, or lacks a role permitted to approve.Have an Owner or Admin approve.
already_approved / already_deniedThis state value was already actioned.Start again with a new random state.

⚠️ A bad redirect URI reports to your registered URL. If the redirect URI you send is not registered, Remote will not redirect to it — doing so would make Remote an open redirector. The error is delivered to your first registered redirect URI instead.
So if you mistype your callback, watching that URL shows you nothing at all. Check your registered URL for an invalid_request error.

Troubleshooting

403 Forbidden on an API call. Your token is valid but lacks the scope for that endpoint. The response tells you exactly what would have worked:

{
  "message": "Forbidden, missing required scope (one of): https://gateway.remote.com/company.manage, timeoff:write, time_and_attendance, all:write got: employment:read, timeoff:read"
}

Any value in the one of list satisfies the endpoint. Re-run the flow requesting one of them.

401 invalid_client on the token request. Either your Basic credentials are wrong, or you sent a client_id in the body that does not match the header.

invalid_grant on the token request. The code was already used, has expired after 5 minutes, or belongs to a different client.


Did this page help you?