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.
Machine-readable version
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.
Prerequisites
- Client ID — the UUID identifier for your API key, used as
client_idin OAuth requests. Find it by clicking "View Details" on your API key in the dashboard. - API key secret — used in the
x-api-keyheader for server-to-server calls (token exchange, introspect, revoke, and data queries). - Redirect URI— your app's callback URL, registered via View Details on your API key. HTTPS required (HTTP allowed for localhost).
OAuth Flow Step by Step#
Base URL: https://<your-eds-host>/api/v1
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}`;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.
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");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.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;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();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.
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | Unique identifier for the employee. |
first_name | string | Yes | Employee’s legal first name. |
last_name | string | Yes | Employee’s legal last name. |
middle_name | string | null | Yes | Employee’s middle name. |
preferred_name | string | null | Yes | Name the employee prefers to go by. |
complete_name | string | Yes | Full display name (computed from first, preferred, and last name). |
department_id | UUID | Yes | ID of the department the employee belongs to. |
department | string | No | Department name (present in OAuth token/introspect and verify responses). |
job_title | string | Yes | Employee’s job title. |
birthday | string | null | Yes | ISO 8601 date string (YYYY-MM-DD). |
start_date | string | null | Yes | ISO 8601 date the employee started. |
name_pronunciation | string | null | Yes | Phonetic guide for pronouncing the employee’s name. |
phone_number | string | null | Yes | Employee’s phone number. |
email | string | null | Yes | Employee’s personal email address. |
company_email | string | Yes | Employee’s company-issued email address. |
timezone | string | null | Yes | IANA timezone identifier (e.g. America/New_York). |
country | string | null | Yes | Country of residence. |
address_1 | string | null | Yes | Primary street address line. |
address_2 | string | null | Yes | Secondary address line (apt, suite, etc.). |
city | string | null | Yes | City of residence. |
state | string | null | Yes | State or province of residence. |
zip_postal_code | string | null | Yes | ZIP or postal code. |
profile_photo_url | string | null | Yes | URL to the employee's profile photo. |
is_active | boolean | Yes | Whether the employee’s account is currently active. |
roles | string[] | Yes | Array of roles within EDS (e.g. ["employee"], ["admin"], ["employee", "developer"]). |
time_employed | string | null | Yes | Human-readable time since start_date (e.g. “2 years, 3 months”). Only present in OAuth token/introspect responses. |
Error Codes#
| Code | Description |
|---|---|
INVALID_REQUEST | Request body is missing or not valid JSON. |
VALIDATION_ERROR | Request body failed schema validation (missing or invalid fields). |
INVALID_GRANT | Authorization code is invalid, expired, already used, or redirect_uri doesn’t match. |
UNAUTHORIZED | Missing or invalid API key in x-api-key header. |
access_denied | Employee 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.