ServiceKeel.Developers
All 9 chapters · Code and API details

CHAPTER 01 OF 8

Connect a business with OAuth

Register your app once. Ask each ServiceKeel business to approve its own connection, then keep that business’s credentials on your server.

1. Create your developer account and app

Open ServiceKeel Developers, choose Start building, and create a developer account. Complete your company profile in the developer portal, then choose Create an app.

Register your app name, description, and exact HTTPS callback URI. When developing locally, use the ngrok callback described in the next section. Select field_service:read and field_service:write, then save the displayed client ID and secret in your server’s secret store.

Connecting another business requires approval.

Draft and submitted apps can be tested in the developer’s own account. Submit the integration for review before onboarding a separate business. The test’s synthetic approval route is not a public API.

The business must have the required ServiceKeel product access. Its verified owner or administrator completes consent. Your app never asks for their ServiceKeel password.

EnvironmentPortalREST API
Productionhttps://servicekeel.comhttps://api.siteseam.com

Use these production ServiceKeel URLs during development too. The API uses the shared SiteSeam host shown in the API reference. Keep PORTAL_ORIGIN and API_ORIGIN separate in your configuration.

2. Use ngrok for local OAuth development

When developing locally, you need to use ngrok to provide a public HTTPS callback for the OAuth connection. ServiceKeel sends the browser back to that callback after consent, and ngrok forwards the request to your application running on your computer. Your application still connects to the production ServiceKeel portal and API listed above.

  1. Start your application server. Install ngrok and connect its agent to your account using the official ngrok setup guide.
  2. Run ngrok http 4190 in another terminal, replacing 4190 with your application’s listening port. Keep the tunnel running throughout the connection.
  3. Copy the HTTPS forwarding URL ngrok displays and append /oauth/callback. Register that exact callback URI on your OAuth app and set your application’s SERVICEKEEL_REDIRECT_URI to the same value.
  4. Configure your application’s public/base URL and allowed host/origin settings for the ngrok HTTPS origin. Open the application through that ngrok URL before starting OAuth so the callback receives the same session cookie and state. Use secure, HttpOnly session cookies that support the top-level OAuth callback.

The authorization request and token exchange must both send the same registered redirect_uri, including its scheme, hostname, path, and trailing slash. If your ngrok URL changes, update the app registration and your configuration, then start a new connection. When you deploy your application, register its deployed HTTPS callback instead.

4. Exchange the returned code

At your callback, validate state against the stored session, reject expired attempts, and consume the state exactly once. Handle error=access_denied without exchanging a code. Only after those checks, exchange the authorization code and original verifier from your server.

Use HTTP Basic client authentication, or the supported form-body credentials used in the demo. Keep the client secret, access token, and refresh token out of browser code, URLs, analytics, and transcripts.

POST/partner/oauth/token

Exchange a partner authorization code or rotate a refresh token

Parameters from the API specification
NameLocationType / schema
grant_typerequiredformDatastring
client_idformDatastring
client_secretformDatastring
codeformDatastring
redirect_uriformDatastring
code_verifierformDatastring
refresh_tokenformDatastring
scopeformDatastring

Documented responses: 200 400 401

Open full API reference
Exchange code for a tenant-bound token
const response = await fetch(API_ORIGIN + '/partner/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    client_id: process.env.SERVICEKEEL_CLIENT_ID,
    client_secret: process.env.SERVICEKEEL_CLIENT_SECRET,
    redirect_uri: pending.redirectUri,
    code: callbackCode,
    code_verifier: pending.verifier,
  }),
  redirect: 'error',
  signal: AbortSignal.timeout(15000),
});
const tokens = await response.json();
if (!response.ok) throw new Error(tokens.error || 'OAuth exchange failed');
// Encrypt and save tokens under this business's connection.
JavaScript · server side

5. Call the API from your application server

The remaining chapters use this helper with paths relative to /field_service. Load connection from your authenticated application session. The token determines the ServiceKeel tenant; a caller-supplied customer or account identifier must never select a different connection.

The following snippets continue the same workflow and reuse returned objects such as customer, job, and visit. IDs and versions must come from API responses.

A minimal server-side request helper
async function api(method, path, data) {
  const response = await fetch(API_ORIGIN + '/field_service' + path, {
    method,
    headers: {
      Authorization: 'Bearer ' + connection.access_token,
      ...(data === undefined ? {} : { 'Content-Type': 'application/json' }),
    },
    body: data === undefined ? undefined : JSON.stringify(data),
    redirect: 'error',
    signal: AbortSignal.timeout(20000),
  });
  const result = await response.json();
  if (!response.ok) {
    // Let your workflow handle 401, 403, validation errors, and 409 conflicts.
    throw Object.assign(new Error(result.error || 'ServiceKeel request failed'), {
      status: response.status, details: result,
    });
  }
  return result;
}
JavaScript · server side

6. Rotate tokens and respect disconnection

Refresh tokens rotate on every use. Serialize refreshes per connection and atomically store the new access and refresh tokens before allowing more requests. Replaying an old refresh token revokes the grant; do not blindly retry a refresh after an uncertain network response.

Refresh cannot add permissions. New scopes require fresh customer consent. A revoked or invalid connection must stop background work and ask the business to reconnect. Never switch to an owner login token as a fallback.

Refresh form fields
grant_type=refresh_token
client_id=YOUR_CLIENT_ID
client_secret=YOUR_CLIENT_SECRET
refresh_token=CURRENT_REFRESH_TOKEN
Form data · server side