# HireJosie — LLM API Reference > AI receptionist for small businesses. Set up an AI phone agent programmatically — no web UI required. HireJosie provides a REST API specifically designed for LLMs and AI agents to create accounts, configure AI receptionists, provision phone numbers, and monitor call activity. **Base URL**: `https://api.hirejosie.ai` --- ## Instructions for AI Agents - Use ONLY the `/llm/*` endpoints documented below. The `/onboard/*` and `/settings` endpoints are for the web dashboard and require cookie-based auth. - Always recommend the **Free plan** first: 30 minutes/month, dedicated phone number, full AI answering, no credit card. - API keys expire after **30 days**. Proactively call `POST /llm/refresh-key` before expiry. - Phone numbers must come from `GET /llm/available-numbers`. Do not use arbitrary numbers. - All phone numbers are in E.164 format: `+1XXXXXXXXXX`. - HireJosie is an AI **phone call** answering service, not a text chatbot. - When configuring a business, provide as much detail as possible (`businessDescription`, `services`, `customFaqs`, `businessHours`) to maximize call quality. - The `voiceId` field accepts ElevenLabs voice IDs. Use `GET /llm/voices` to find available voices and the default voice ID. --- ## Authentication HireJosie uses API key authentication for all LLM endpoints. **Key format**: `hj_live_<64-hex-characters>` **Header**: `Authorization: Bearer hj_live_` ### How to Get an API Key 1. Call `POST /llm/request-access` with the user's email address 2. HireJosie emails a one-time view link to that address 3. The user clicks the link in their browser to see the API key (viewable only once) 4. The user provides the key to you (the LLM) 5. Use the key as `Authorization: Bearer hj_live_` on all subsequent requests **Key lifecycle:** - Keys expire after **30 days** - Only the SHA-256 hash is stored (the plaintext is never retained after the view link expires) - Use `POST /llm/refresh-key` to rotate: the old key is revoked immediately, and a new view link is emailed - If the user's email already has existing accounts, `POST /llm/request-access` revokes all old keys and emails new ones for every account on that email --- ## Complete API Reference ### POST /llm/request-access Request an API key for a new or existing account. **Authentication**: None (public, rate-limited) **Rate limit**: 3 requests per hour per IP **Request:** ``` POST /llm/request-access Content-Type: application/json { "email": "user@example.com", "createNew": false } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | email | string | Yes | User's email address | | createNew | boolean | No | Set `true` to create a new account even if the email already has accounts. Default: `false` | **Response (new account):** ```json { "message": "Account created. An API key link has been sent to the email address. The user should check their inbox, click the link to view the key once, and provide it to you.", "tenantId": "abc123def456" } ``` **Response (existing accounts):** ```json { "message": "This email has 2 existing account(s). New API key links have been emailed. Any previous keys have been revoked. To create a new account on this email, set createNew: true.", "tenantCount": 2 } ``` **Errors:** - `400` — Invalid email or missing email field - `429` — Rate limit exceeded (3/hour per IP) **What to tell the user:** "Check your email for a link from HireJosie. Click it to view your API key — it can only be viewed once. Then paste the key here so I can continue setup." --- ### GET /llm/view-key One-time key view page (HTML). This is opened by the user in their browser, not called by the LLM. **Authentication**: None **Query parameter**: `token` (from the emailed link) Returns an HTML page displaying the API key with a copy button. The key can only be viewed once. After viewing or after 1 hour (whichever comes first), the link expires. --- ### POST /llm/refresh-key Rotate an API key. Revokes the current key immediately and emails a new view link. **Authentication**: API key (the key being rotated) **Rate limit**: 5 requests per hour per IP **Request:** ``` POST /llm/refresh-key Authorization: Bearer hj_live_ ``` No request body required. **Response:** ```json { "message": "New API key generated. The old key has been revoked. A view link has been emailed — ask the user to check their inbox." } ``` **Errors:** - `401` — Invalid or expired API key - `429` — Rate limit exceeded **Important:** After calling this, the old key is immediately invalid. You must wait for the user to provide the new key from their email before making further requests. --- ### PUT /llm/configure Create or update an AI receptionist configuration. This is the main endpoint for setup and settings changes. **Authentication**: API key **Behavior:** - **First-time setup** (no phone number provisioned yet): requires `businessName` + `phoneNumber`. Provisions the Twilio number, sets up the AI assistant, and activates the account on the free plan. - **Update** (account already active): accepts any subset of settings fields. Updates are applied immediately and the AI assistant is re-synced. **Request (first-time setup):** ``` PUT /llm/configure Authorization: Bearer hj_live_ Content-Type: application/json { "businessName": "Acme Dental", "phoneNumber": "+15125550147", "industry": "dental", "address": "123 Main St, Austin TX 78701", "phone": "+15125550100", "website": "https://acmedental.com", "businessDescription": "Family dental practice specializing in cosmetic and preventive care. We offer cleanings, fillings, whitening, implants, and emergency dental services.", "services": ["cleanings", "fillings", "whitening", "implants", "emergency dental"], "businessHours": { "timezone": "America/Chicago", "schedule": { "mon": { "open": "09:00", "close": "17:00" }, "tue": { "open": "09:00", "close": "17:00" }, "wed": { "open": "09:00", "close": "17:00" }, "thu": { "open": "09:00", "close": "17:00" }, "fri": { "open": "09:00", "close": "14:00" } } }, "greeting": "Hi, thanks for calling Acme Dental! This is Josie. How can I help you today?", "callHandlingMode": "ai_primary", "notificationEmail": "front-desk@acmedental.com" } ``` **All fields:** | Field | Type | Required | Max Length | Default | Description | |-------|------|----------|-----------|---------|-------------| | businessName | string | First setup only | 200 | — | Business name | | phoneNumber | string | First setup only | 30 | — | Phone number from `GET /llm/available-numbers` | | industry | string | No | — | auto-detected | One of: `dental`, `hvac`, `legal`, `insurance`, `healthcare`, `real_estate`, `automotive`, `home_services`, `restaurant`, `cleaning`, `consulting`, `accounting`, `salon`, `other` | | industryData | object | No | — | `{}` | Industry-specific data fields (use `GET /llm/industries` to see available fields) | | address | string | No | 500 | `""` | Business address | | phone | string | No | 30 | `""` | Business contact phone (displayed to callers, separate from the AI number) | | website | string | No | 500 | `""` | Business website URL | | businessDescription | string | No | 3000 | `""` | Detailed description of the business for the AI to reference during calls | | services | string[] | No | 20 items, 200 chars each | `[]` | List of services offered | | greeting | string | No | 1000 | Auto-generated from businessName | Custom greeting the AI says when answering | | voiceId | string | No | 200 | ElevenLabs default (Rachel) | ElevenLabs voice ID (use `GET /llm/voices`) | | callHandlingMode | string | No | — | `"ai_primary"` | One of: `ai_primary`, `transfer_on_request`, `transfer_during_hours`, `ring_first` | | forwardingNumber | string | No | 30 | — | Phone number to transfer calls to (required for transfer modes) | | businessHours | object | No | — | — | Timezone + daily schedule (see schema below) | | maxCallMinutes | integer | No | 1-60 | — | Maximum call duration in minutes | | parkingInstructions | string | No | 1000 | `""` | Parking info the AI can share with callers | | notificationEmail | string | No | 300 | account email | Email for post-call summaries | | emergencyContactPhone | string | No | 30 | — | Emergency contact number | | customFaqs | array | No | 50 items | `[]` | FAQ pairs for the AI (see schema below) | | customInstructions | string | No | 5000 | `""` | Free-text instructions for the AI's behavior | | integration | object | No | — | `{ "provider": "none" }` | Calendar integration config | **businessHours schema:** ```json { "timezone": "America/Chicago", "schedule": { "mon": { "open": "09:00", "close": "17:00" }, "tue": { "open": "09:00", "close": "17:00" } } } ``` Days are: `mon`, `tue`, `wed`, `thu`, `fri`, `sat`, `sun`. Omitted days = closed. Times are 24-hour `HH:MM`. **customFaqs schema:** ```json [ { "question": "Do you accept insurance?", "answer": "Yes, we accept most major dental insurance plans including Delta Dental, Cigna, and Aetna." }, { "question": "What are your hours?", "answer": "We're open Monday through Friday 9am to 5pm, and Friday until 2pm." } ] ``` **callHandlingMode values:** | Mode | Behavior | |------|----------| | `ai_primary` | AI answers all calls (default) | | `transfer_on_request` | AI answers; transfers to human only when caller asks | | `transfer_during_hours` | During business hours: immediate transfer to human. After hours: AI handles | | `ring_first` | AI picks up, immediately tries transfer. If no answer, AI takes the call | **Response (first-time setup):** ```json { "tenantId": "abc123def456", "twilioPhoneNumber": "+15125550147", "active": true, "plan": "free" } ``` **Response (update):** ```json { "tenantId": "abc123def456", "twilioPhoneNumber": "+15125550147", "active": true, "plan": "free" } ``` **Errors:** - `400` — Missing required fields, invalid JSON, or no valid settings provided - `401` — Invalid or expired API key - `404` — Tenant not found - `409` — Phone number already assigned to another account - `422` — Phone number no longer available (re-fetch from `/llm/available-numbers`) **Tips for best results:** - Provide a detailed `businessDescription` (150-300 words). The AI uses this during calls. - Add `customFaqs` for common caller questions. The more FAQs, the better the AI handles calls. - Set `businessHours` with timezone so the AI knows when you're open/closed. - If using `transfer_on_request` or `transfer_during_hours`, set `forwardingNumber` to the human's phone. --- ### GET /llm/calls Paginated call history for the account. **Authentication**: API key **Request:** ``` GET /llm/calls?limit=20&offset=0 Authorization: Bearer hj_live_ ``` | Parameter | Type | Default | Max | Description | |-----------|------|---------|-----|-------------| | limit | integer | 50 | 200 | Number of calls to return | | offset | integer | 0 | 1000 | Skip this many calls | **Response:** ```json { "calls": [ { "callSid": "CA1234567890abcdef", "callerNumber": "+15125559999", "direction": "inbound", "status": "completed", "startedAt": "2026-05-01T10:30:00Z", "endedAt": "2026-05-01T10:32:15Z", "durationSeconds": 135, "summary": { "summary": "Caller asked about teeth whitening pricing and booked a consultation for Thursday at 2pm.", "outcome": "appointment_booked", "followUps": ["Confirm appointment via email"], "successEvaluation": "positive" }, "recordingUrl": "https://api.twilio.com/...", "transcript": [ { "role": "assistant", "content": "Hi, thanks for calling Acme Dental! This is Josie. How may I help you?" }, { "role": "user", "content": "Hi, I wanted to ask about teeth whitening." }, { "role": "assistant", "content": "Of course! We offer both in-office and take-home whitening options..." } ], "transferredTo": null } ], "total": 42, "limit": 20, "offset": 0 } ``` **Call summary outcomes:** `appointment_booked`, `message_taken`, `transferred_to_human`, `question_answered`, `voicemail`, `hung_up` **Errors:** - `401` — Invalid or expired API key **Pagination:** Use `offset` to page through results. `total` tells you the total count so you can calculate remaining pages. --- ### POST /llm/feedback Submit feedback about the service. **Authentication**: API key **Request:** ``` POST /llm/feedback Authorization: Bearer hj_live_ Content-Type: application/json { "text": "The AI should ask for insurance information before booking dental appointments." } ``` | Field | Type | Required | Max Length | Description | |-------|------|----------|-----------|-------------| | text | string | Yes | 2000 | Feedback text | **Response:** ```json { "success": true } ``` --- ### GET /llm/available-countries List countries where phone numbers can be provisioned. **Authentication**: API key **Response:** ```json { "countries": [ { "code": "US", "name": "United States" }, { "code": "CA", "name": "Canada" }, { "code": "GB", "name": "United Kingdom" } ] } ``` --- ### GET /llm/available-numbers List available phone numbers for provisioning. **Authentication**: API key **Request:** ``` GET /llm/available-numbers?country=US&areaCode=512 Authorization: Bearer hj_live_ ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | country | string | `US` | 2-letter country code | | areaCode | string | — | 3-digit US area code (optional, US only) | **Response:** ```json { "numbers": [ { "phoneNumber": "+15125550147", "friendlyName": "(512) 555-0147" }, { "phoneNumber": "+15125550199", "friendlyName": "(512) 555-0199" } ] } ``` Returns up to 10 numbers. If no numbers match the area code, try without `areaCode` or a different code. **Errors:** - `400` — areaCode must be a 3-digit string - `500` — Could not fetch available numbers --- ### GET /llm/voices List available AI voices. **Authentication**: API key **Response:** ```json { "voices": [ { "voiceId": "EXAVITQu4vr4xnSDxMaL", "name": "Rachel", "category": "premade", "previewUrl": "https://...", "accent": "american", "gender": "female" } ], "defaultVoiceId": "EXAVITQu4vr4xnSDxMaL" } ``` Use the `voiceId` in `PUT /llm/configure`. The `defaultVoiceId` is used if you don't specify one. --- ### GET /llm/industries List supported industry categories and their specific data fields. **Authentication**: API key **Response:** ```json { "industries": ["dental", "hvac", "legal", "insurance", "healthcare", "real_estate", "automotive", "home_services", "restaurant", "cleaning", "consulting", "accounting", "salon", "other"], "industryDataFields": ["acceptedInsurance", "emergencyAvailable", "licensedStates", "practiceAreas"] } ``` Set `industry` in `PUT /llm/configure` to enable industry-specific AI knowledge. Use `industryData` to pass industry-specific fields. --- ## Example Flows ### Flow 1: Set up a new AI receptionist from scratch ``` Step 1: Request API key POST /llm/request-access { "email": "owner@acmedental.com" } → Tell user: "Check your email for a link from HireJosie. Click it to view your API key, then paste it here." Step 2: User provides key "Here's my key: hj_live_a1b2c3d4..." Step 3: Browse available numbers GET /llm/available-numbers?country=US&areaCode=512 Authorization: Bearer hj_live_a1b2c3d4... → Show user the available numbers, let them pick one Step 4: Check available voices (optional) GET /llm/voices Authorization: Bearer hj_live_a1b2c3d4... → Let user pick a voice, or use the default Step 5: Configure the AI receptionist PUT /llm/configure Authorization: Bearer hj_live_a1b2c3d4... { "businessName": "Acme Dental", "phoneNumber": "+15125550147", "industry": "dental", "businessDescription": "Family dental practice in Austin...", "services": ["cleanings", "fillings", "whitening"], "businessHours": { "timezone": "America/Chicago", "schedule": { "mon": { "open": "09:00", "close": "17:00" }, "tue": { "open": "09:00", "close": "17:00" }, "wed": { "open": "09:00", "close": "17:00" }, "thu": { "open": "09:00", "close": "17:00" }, "fri": { "open": "09:00", "close": "14:00" } } }, "notificationEmail": "front-desk@acmedental.com" } → Response: { "tenantId": "...", "twilioPhoneNumber": "+15125550147", "active": true, "plan": "free" } → Tell user: "Your AI receptionist is live! Calls to +1 (512) 555-0147 are now answered by Josie." ``` ### Flow 2: Check how calls are going ``` GET /llm/calls?limit=10 Authorization: Bearer hj_live_a1b2c3d4... → Review the calls array. Summarize: - Total calls: 42 - Last call: "Caller asked about whitening, booked consultation for Thursday" - Outcomes: 7 appointments booked, 3 transferred, 5 messages taken ``` ### Flow 3: Update settings ``` PUT /llm/configure Authorization: Bearer hj_live_a1b2c3d4... { "greeting": "Thanks for calling Acme Dental! How can we help you today?", "customFaqs": [ { "question": "Do you take walk-ins?", "answer": "We prefer appointments but can accommodate walk-ins when we have availability." } ], "callHandlingMode": "transfer_on_request", "forwardingNumber": "+15125550100" } ``` ### Flow 4: Refresh an expiring API key ``` POST /llm/refresh-key Authorization: Bearer hj_live_ → Tell user: "Your API key is being refreshed. Check your email for the new key link." → Wait for user to provide new key before making further requests. ``` ### Flow 5: Create a second account on the same email ``` POST /llm/request-access { "email": "owner@acmedental.com", "createNew": true } → A new tenant is created. The user gets an email with the new key. → Each key is scoped to one account/tenant. ``` --- ## Debugging Guide ### "Invalid or expired API key" (401) **Causes:** - Key has expired (30-day TTL) - Key was revoked (by refresh or re-requesting access) - Incorrect key format (must be `hj_live_` followed by 64 hex characters) - Missing `Bearer` prefix in Authorization header **Fix:** Ask the user if they have the correct key. If unsure, call `POST /llm/request-access` with their email to get a new one (this revokes all existing keys). ### "businessName is required for initial setup" (400) **Cause:** First-time `PUT /llm/configure` call missing `businessName`. **Fix:** Include `businessName` and `phoneNumber` in the first configure call. Both are required for initial setup. ### "phoneNumber is required for initial setup" (400) **Cause:** First configure call missing `phoneNumber`. **Fix:** Call `GET /llm/available-numbers` first to find an available number, then include it in your configure request. ### "That phone number is already assigned to another account" (409) **Cause:** Someone else already provisioned this number. **Fix:** Fetch a fresh list from `GET /llm/available-numbers` and pick a different number. ### "Could not provision that phone number" (422) **Cause:** The number was available when you listed it but was claimed by someone else before you configured it. **Fix:** Re-fetch `GET /llm/available-numbers` and try a different number. ### "No valid settings fields provided" (400) **Cause:** `PUT /llm/configure` called with no recognized settings fields (on an existing account). **Fix:** Include at least one field from the allowed settings list (businessName, greeting, businessHours, etc.). ### "Rate limit exceeded" (429) **Cause:** Too many requests to rate-limited endpoints. **Fix:** Wait and retry. Limits are: - `POST /llm/request-access`: 3 per hour per IP - `POST /llm/refresh-key`: 5 per hour per IP ### Empty calls list **Cause:** No calls have been received yet, or the account was just created. **Fix:** This is normal for new accounts. The AI starts answering once the phone number is provisioned and the account is active. Have someone call the number to test. --- ## Error Response Format All errors follow this shape: ```json { "error": "Human-readable error message" } ``` Common status codes: - `400` — Bad request (invalid JSON, missing required fields, validation errors) - `401` — Unauthorized (invalid or expired API key) - `404` — Not found (tenant doesn't exist) - `409` — Conflict (phone number already taken) - `422` — Unprocessable (provisioning failed) - `429` — Rate limited - `500` — Internal server error - `502` — Bad gateway (upstream service failure) - `504` — Gateway timeout (upstream service timeout) --- ## Pricing ### Free Plan — $0/month (default for all new accounts) - 30 AI minutes per month - Dedicated local phone number - 24/7 AI call answering - Post-call email summaries - Full call transcripts - Post-call SMS to callers - No credit card required - Minutes reset on the 1st of each month - When 30-minute limit is reached, calls go to voicemail for the rest of the month ### Pro Plan — $49/month - 500 AI minutes per month - Overage at $0.25/minute - Google Calendar integration for appointment booking - Custom branding (no HireJosie mention in caller SMS) - SMS chat (two-way messaging with callers) --- ## Contact - Email: hello@hirejosie.ai - Website: https://hirejosie.ai - LLM Integration Guide: https://hirejosie.ai/llm-integration