JLS
Developer Docs

LLM Integration Guide

A self-contained reference for AI assistants helping developers integrate with EDS. This page consolidates everything needed to implement OAuth authentication and query the employee directory — no need to reference other pages.

What is EDS?#

JLS Crewbook (formerly known as the Employee Directory Service, or EDS) is JLS Trading Co.'s centralized employee directory and authentication service. It provides two capabilities to internal applications: OAuth-based single sign-on (so employees can log into your app using their company credentials) and a REST API for querying employee directory data (names, departments, contact info, roles).

Authentication Methods#

1. API Key Authentication

For server-to-server calls (querying employees, exchanging OAuth codes, introspecting sessions). Pass your API key in the x-api-key request header.

2. OAuth Authorization Code Flow

For authenticating employees. Your app redirects employees to EDS to log in, receives a short-lived authorization code, and exchanges it for a 24-hour session token. The session token can be introspected to get the employee's profile.

OAuth Flow Step by Step#

Base URL: https://<your-eds-host>/api/v1

1

Redirect to authorize

When an employee wants to log in, redirect their browser to the EDS authorize endpoint. Include your Client ID (UUID from View Details on your API key — not the secret key), your registered redirect_uri, and a random state value for CSRF protection.

const state = crypto.randomUUID();
sessionStorage.setItem("oauth_state", state);

const params = new URLSearchParams({
  client_id: "YOUR_CLIENT_ID",   // UUID from API key View Details
  redirect_uri: "https://yourapp.com/auth/callback",
  state,
});

window.location.href = `https://<your-eds-host>/api/v1/oauth/authorize?${params}`;
2

Employee logs in and consents

EDS presents the employee with a login form (Google OAuth) and a consent screen showing your app’s name. This step happens entirely within EDS — your app waits for the redirect back. If the employee has previously consented, the consent screen is skipped automatically.

3

Handle the callback

EDS redirects back to your redirect_uri with a short-lived authorization code and the state you sent. Verify the state matches to prevent CSRF, then extract the code. If the employee denied consent, you’ll receive error=access_denied instead of a code.

// In your callback route handler
const params = new URLSearchParams(window.location.search);

// Check for access denied
if (params.get("error") === "access_denied") {
  const description = params.get("error_description") ?? "Access denied";
  throw new Error(description);
}

const code = params.get("code");
const returnedState = params.get("state");
const savedState = sessionStorage.getItem("oauth_state");

if (!code || returnedState !== savedState) {
  throw new Error("Invalid OAuth callback — state mismatch or missing code");
}

sessionStorage.removeItem("oauth_state");
4

Exchange code for session token (server-side)

POST the authorization code to the token endpoint within 5 minutes. This MUST happen server-side — never expose your API key to the browser. You’ll receive a 24-hour session token and the authenticated employee’s full profile.

const response = await fetch(`https://<your-eds-host>/api/v1/oauth/token`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": process.env.EDS_API_KEY,
  },
  body: JSON.stringify({
    grant_type: "authorization_code",
    code,
    redirect_uri: "https://yourapp.com/auth/callback",
  }),
});

const { session_token, employee, expires_at } = await response.json();
// Store session_token securely (e.g. httpOnly cookie)
// employee contains: id, first_name, last_name, company_email, roles, etc.
5

Validate sessions with introspect

On subsequent requests, verify the session is still active by calling the introspect endpoint. This returns the full employee object if valid, or { active: false } if expired/revoked. Always returns HTTP 200 — check the active field.

const response = await fetch(`https://<your-eds-host>/api/v1/oauth/introspect`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": process.env.EDS_API_KEY,
  },
  body: JSON.stringify({ session_token }),
});

const data = await response.json();

if (!data.active) {
  // Token expired or revoked redirect employee to login
  return redirectToLogin();
}

// data.employee contains the full employee profile
// data.expires_at is the session expiration timestamp
const { employee, expires_at } = data;
6

Query employee data

Use your API key to query the employee directory. List employees with pagination and search, get a single employee by ID, or verify an employee by email.

// List employees (paginated, searchable)
const list = await fetch(`https://<your-eds-host>/api/v1/employees?page=1&limit=20&search=jane`, {
  headers: { "x-api-key": process.env.EDS_API_KEY },
});
const { employees, pagination } = await list.json();

// Get single employee by ID
const detail = await fetch(`https://<your-eds-host>/api/v1/employees/EMPLOYEE_UUID`, {
  headers: { "x-api-key": process.env.EDS_API_KEY },
});
const employee = await detail.json();

// Verify employee by company email
const check = await fetch(`https://<your-eds-host>/api/v1/verify?email=jane.smith@company.com`, {
  headers: { "x-api-key": process.env.EDS_API_KEY },
});
const { verified, employee: emp } = await check.json();
7

Revoke on logout

When an employee logs out of your app, revoke their session token. This endpoint is idempotent — revoking an already-revoked or unknown token returns the same 200 response without error.

await fetch(`https://<your-eds-host>/api/v1/oauth/revoke`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": process.env.EDS_API_KEY,
  },
  body: JSON.stringify({ session_token }),
});

// Clear local session state
res.clearCookie("session_token");

Endpoints Reference#

Start the OAuth flow

Exchange code for session token

Check if a session is valid

Revoke a session token

List employees with pagination and search

Get a single employee by ID

Verify an employee by email

Employee Object#

All fields are present in every response. Nullable fields are marked with | null.

NameTypeRequiredDescription
idUUIDYesUnique identifier for the employee.
first_namestringYesEmployee’s legal first name.
last_namestringYesEmployee’s legal last name.
middle_namestring | nullYesEmployee’s middle name.
preferred_namestring | nullYesName the employee prefers to go by.
complete_namestringYesFull display name (computed from first, preferred, and last name).
department_idUUIDYesID of the department the employee belongs to.
departmentstringNoDepartment name (present in OAuth token/introspect and verify responses).
job_titlestringYesEmployee’s job title.
birthdaystring | nullYesISO 8601 date string (YYYY-MM-DD).
start_datestring | nullYesISO 8601 date the employee started.
name_pronunciationstring | nullYesPhonetic guide for pronouncing the employee’s name.
phone_numberstring | nullYesEmployee’s phone number.
emailstring | nullYesEmployee’s personal email address.
company_emailstringYesEmployee’s company-issued email address.
timezonestring | nullYesIANA timezone identifier (e.g. America/New_York).
countrystring | nullYesCountry of residence.
address_1string | nullYesPrimary street address line.
address_2string | nullYesSecondary address line (apt, suite, etc.).
citystring | nullYesCity of residence.
statestring | nullYesState or province of residence.
zip_postal_codestring | nullYesZIP or postal code.
profile_photo_urlstring | nullYesURL to the employee's profile photo.
is_activebooleanYesWhether the employee’s account is currently active.
rolesstring[]YesArray of roles within EDS (e.g. ["employee"], ["admin"], ["employee", "developer"]).
time_employedstring | nullYesHuman-readable time since start_date (e.g. “2 years, 3 months”). Only present in OAuth token/introspect responses.

Error Codes#

CodeDescription
INVALID_REQUESTRequest body is missing or not valid JSON.
VALIDATION_ERRORRequest body failed schema validation (missing or invalid fields).
INVALID_GRANTAuthorization code is invalid, expired, already used, or redirect_uri doesn’t match.
UNAUTHORIZEDMissing or invalid API key in x-api-key header.
access_deniedEmployee denied consent or their account is inactive (returned as redirect query param).

Security Checklist#

  • *Always validate the state parameter in the callback to prevent CSRF attacks.
  • *Exchange authorization codes server-side — never expose your API key to the browser.
  • *Store session tokens in httpOnly cookies to prevent XSS theft.
  • *Call /api/v1/oauth/revoke when an employee logs out of your app.
  • *When an employee is deactivated in EDS, all their active sessions are automatically revoked.
  • *Authorization codes are single-use and expire after 5 minutes.
  • *Session tokens are valid for 24 hours from issuance.
  • *Redirect URIs must be registered in advance and match exactly (including scheme, host, path, and trailing slash).
  • *HTTPS is required for redirect URIs (HTTP allowed only for localhost during development).

App Registry — LLM Documentation#

Internal apps registered in the App Registry can provide an LLM-friendly documentation URL. When an app registers, it can include a link to machine-readable documentation (such as an llms.txt file or similar) that helps AI assistants understand how to interact with the app.

You can find registered apps and their documentation links in the Applications dashboard.