# JLS Crewbook — LLM Integration Guide > Base URL: https:///api/v1 > Human-readable version: https:///developers/llms JLS Crewbook (formerly known as the Employee Directory Service, or EDS) is JLS Trading Co.'s centralized employee directory and authentication service. It provides 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. ## Authentication ### API Key Authentication For server-to-server calls. Pass your API key in the x-api-key request header: ``` x-api-key: YOUR_API_KEY ``` ### 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. ## Prerequisites - **Client ID** — UUID identifier for your API key (found via "View Details" on your API key in the dashboard). Used as client_id in OAuth requests. - **API key secret** — used in the x-api-key header for server-to-server calls (token exchange, introspect, revoke, data queries). - **Redirect URI** — your app's callback URL, registered via View Details. HTTPS required (HTTP allowed for localhost). Create a read-scope API key from the /dashboard/api-keys page. Need admin-scope? Contact your EDS administrator. ## OAuth Flow Step by Step ### 1. Redirect to Authorize Redirect the employee's browser to the authorize endpoint: ``` GET /api/v1/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/auth/callback&state=RANDOM_STATE ``` ```javascript const state = crypto.randomUUID(); sessionStorage.setItem("oauth_state", state); const params = new URLSearchParams({ client_id: "YOUR_CLIENT_ID", redirect_uri: "https://yourapp.com/auth/callback", state, }); window.location.href = \`https:///api/v1/oauth/authorize?\${params}\`; ``` ### 2. Employee Logs In and Consents EDS presents a login form (Google OAuth) and consent screen. If the employee previously consented, the consent screen is skipped. This step happens entirely within EDS. ### 3. Handle the Callback EDS redirects back to your redirect_uri with code and state: ``` https://yourapp.com/auth/callback?code=AUTH_CODE&state=RANDOM_STATE ``` If the employee denied consent: `?error=access_denied&error_description=...` ```javascript const params = new URLSearchParams(window.location.search); if (params.get("error") === "access_denied") { throw new Error(params.get("error_description") ?? "Access denied"); } 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"); } sessionStorage.removeItem("oauth_state"); ``` ### 4. Exchange Code for Session Token (Server-Side) POST the code within 5 minutes. MUST be server-side — never expose your API key to the browser. ```bash curl -X POST https:///api/v1/oauth/token \\ -H "Content-Type: application/json" \\ -H "x-api-key: YOUR_API_KEY" \\ -d '{"grant_type":"authorization_code","code":"AUTH_CODE","redirect_uri":"https://yourapp.com/auth/callback"}' ``` ```javascript const response = await fetch("https:///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(); ``` Response (200): ```json { "session_token": "eds_tok_...", "expires_at": "2026-04-10T12:00:00.000Z", "employee": { "id": "00000000-0000-0000-0000-000000000001", "first_name": "Jane", "last_name": "Smith", "middle_name": null, "preferred_name": null, "complete_name": "Jane Smith", "department_id": "00000000-0000-0000-0000-000000000010", "department": "Engineering", "job_title": "Software Engineer", "birthday": "1990-05-15", "start_date": "2023-01-10", "name_pronunciation": null, "phone_number": "+1-555-0123", "email": "jane.personal@gmail.com", "company_email": "jane.smith@jlstradingco.com", "timezone": "America/New_York", "country": "US", "address_1": "123 Main St", "address_2": "Apt 4B", "city": "New York", "state": "NY", "zip_postal_code": "10001", "profile_photo_url": null, "is_active": true, "roles": ["employee"], "time_employed": "3 years, 3 months" } } ``` ### 5. Validate Sessions with Introspect Check if a session token is still valid. Always returns HTTP 200 — check the active field. ```bash curl -X POST https:///api/v1/oauth/introspect \\ -H "Content-Type: application/json" \\ -H "x-api-key: YOUR_API_KEY" \\ -d '{"session_token":"eds_tok_..."}' ``` ```javascript const response = await fetch("https:///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 to login } const { employee, expires_at } = data; ``` Active response (200): ```json { "active": true, "employee": { ...full employee object... }, "expires_at": "2026-04-10T12:00:00.000Z" } ``` Inactive response (200): ```json { "active": false } ``` ### 6. Query Employee Data Use your API key to query the employee directory. List employees (paginated): ```bash curl https:///api/v1/employees?page=1&limit=20&search=jane \\ -H "x-api-key: YOUR_API_KEY" ``` Response (200): ```json { "employees": [ { ...employee objects... } ], "pagination": { "page": 1, "limit": 20, "total": 84, "total_pages": 5 } } ``` Get single employee: ```bash curl https:///api/v1/employees/EMPLOYEE_UUID \\ -H "x-api-key: YOUR_API_KEY" ``` Verify employee by email: ```bash curl "https:///api/v1/verify?email=jane.smith@company.com" \\ -H "x-api-key: YOUR_API_KEY" ``` Response (200): ```json { "verified": true, "employee": { ...employee object... } } ``` ### 7. Revoke on Logout Revoke the session token when the employee logs out. Idempotent — safe to call on already-revoked tokens. ```bash curl -X POST https:///api/v1/oauth/revoke \\ -H "Content-Type: application/json" \\ -H "x-api-key: YOUR_API_KEY" \\ -d '{"session_token":"eds_tok_..."}' ``` Response (200): ```json { "message": "Token revoked" } ``` ## Endpoints Reference ### GET /api/v1/oauth/authorize Start the OAuth flow. Redirects employee to login and consent. Query params: client_id (UUID, required), redirect_uri (URL, required), state (string, required). Success: redirects to redirect_uri with code and state. Failure: redirects to redirect_uri with error=access_denied and error_description. ### POST /api/v1/oauth/token Exchange authorization code for session token. Headers: x-api-key (required), Content-Type: application/json (required). Body: { "grant_type": "authorization_code", "code": "...", "redirect_uri": "..." } Response: { "session_token": "eds_tok_...", "employee": { ...full profile... }, "expires_at": "..." } ### POST /api/v1/oauth/introspect Check if a session token is valid. Always returns HTTP 200. Headers: x-api-key (required), Content-Type: application/json (required). Body: { "session_token": "eds_tok_..." } Active: { "active": true, "employee": { ... }, "expires_at": "..." } Inactive: { "active": false } ### POST /api/v1/oauth/revoke Revoke a session token. Idempotent. Always returns HTTP 200. Headers: x-api-key (required), Content-Type: application/json (required). Body: { "session_token": "eds_tok_..." } Response: { "message": "Token revoked" } ### GET /api/v1/employees List employees with pagination and search. Headers: x-api-key (required). Query params: page (number, default 1), limit (number, default 20, max 100), department_id (uuid), search (string), is_active (boolean, default true). Response: { "employees": [...], "pagination": { "page", "limit", "total", "total_pages" } } ### GET /api/v1/employees/:id Get single employee by UUID. Headers: x-api-key (required). Path params: id (UUID, required). Query params: include ("emergency_contacts" to include emergency contacts). Response: single employee object. 404 if not found. ### GET /api/v1/verify Verify an employee by company email. Headers: x-api-key (required). Query params: email (string, required). Response: { "verified": true/false, "employee": { ... } } ## Employee Object Fields | Field | Type | Description | |-------|------|-------------| | id | UUID | Unique identifier | | first_name | string | Legal first name | | last_name | string | Legal last name | | middle_name | string or null | Middle name | | preferred_name | string or null | Preferred name | | complete_name | string | Full display name (computed) | | department_id | UUID | Department identifier | | department | string | Department name (in OAuth/verify responses) | | job_title | string | Job title | | birthday | string or null | ISO 8601 date (YYYY-MM-DD) | | start_date | string or null | ISO 8601 start date | | name_pronunciation | string or null | Phonetic pronunciation guide | | phone_number | string or null | Phone number | | email | string or null | Personal email | | company_email | string | Company email | | timezone | string or null | IANA timezone (e.g. America/New_York) | | country | string or null | Country of residence | | address_1 | string or null | Street address | | address_2 | string or null | Secondary address (apt, suite) | | city | string or null | City | | state | string or null | State or province | | zip_postal_code | string or null | ZIP/postal code | | profile_photo_url | string or null | Profile photo URL | | is_active | boolean | Whether account is active | | roles | string[] | Roles within EDS (e.g. ["employee"], ["admin"]) | | time_employed | string or null | Human-readable tenure (e.g. "2 years, 3 months"). Only 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 account is inactive (returned as redirect query param) | ## Security Notes - 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 (scheme, host, path, trailing slash). - HTTPS is required for redirect URIs (HTTP allowed only for localhost during development).