Agent API Reference
The AI Agent System is a conversational AI framework exposed via GraphQL on the background-tasks service. Agents support multi-turn conversations, RAG-powered document search, tool execution, real-time streaming, voice integration, and billing-integrated credit consumption. Agents can be company-scoped (employer) or user-owned (job seeker — companyId is NULL).
Endpoints
| Environment | HTTP | WebSocket | Voice WebSocket |
|---|---|---|---|
| Production | https://ai.aiqlick.com/graphql | wss://ai.aiqlick.com/graphql | wss://ai.aiqlick.com/voice/ws/{conversation_id} |
| Development | https://ai-dev.aiqlick.com/graphql | wss://ai-dev.aiqlick.com/graphql | wss://ai-dev.aiqlick.com/voice/ws/{conversation_id} |
All requests require a JWT token: Authorization: Bearer <token>.
Agent CRUD
Create Agent
mutation CreateAgent($input: CreateAgentInput!, $createdById: String!) {
createAgent(input: $input, createdById: $createdById) {
success
message
agent { id name type status }
}
}
CreateAgentInput fields:
| Field | Type | Default | Description |
|---|---|---|---|
companyId | String | null | Company UUID (required for employers, omit for job seekers) |
name | String! | -- | Display name (unique per company or per user) |
type | AgentType! | -- | RECRUITMENT_ASSISTANT, TALENT_SEARCH, JOB_MATCHING, INTERVIEW_PREP, DATA_ANALYST, CUSTOM |
description | String | null | Agent description |
llmModel | String | anthropic.claude-3-sonnet-20240229-v1:0 | Bedrock model ID |
temperature | Float | 0.7 | LLM temperature |
maxTokens | Int | 4096 | Max output tokens |
systemPrompt | String | null | Custom system prompt |
ragEnabled | Boolean | false | Enable RAG document search |
ragTopK | Int | 5 | Number of RAG chunks to retrieve |
ragMinScore | Float | 0.7 | Minimum similarity score for RAG |
enabledTools | [String] | [] | Tool types the agent can invoke. Default-agent recruiter palette = 38 tools (every implemented tool in tool_service.py:_HANDLER_REGISTRY); default-agent job-seeker palette = 19 tools mirroring JOB_SEEKER_ALLOWED_TOOL_TYPES (see ViewMode-Aware Behaviour). Core types: RAG_SEARCH, DATABASE_QUERY, TALENT_SEARCH, JOB_SEARCH, COMPANY_INFO, CANDIDATE_SEARCH, PIPELINE_OVERVIEW, INTERVIEW_SCHEDULE, MEETING_LIST, CREATE_TALENT, UPDATE_TALENT, UPDATE_CANDIDATE_STATUS, ADD_CANDIDATE_TO_JOB, ADD_TALENT_NOTE, CV_EXTRACTION, JOB_PARSING, RECOMMEND_JOBS_FOR_TALENT, RECOMMEND_TALENTS_FOR_JOB, GENERATE_JOB_DESCRIPTION, HIRING_ANALYTICS, TALENT_POOL_INSIGHTS, GET_TALENT_CV_DETAILS, SHORTLIST_CANDIDATES, COMPARE_CANDIDATES, GET_MEETING_INSIGHT, CV_READ, CV_UPDATE, CV_ANALYSIS, GET_MATCH_SCORES, GET_ME, GET_MY_STATS, GET_MY_SUBSCRIPTION, GET_MY_RECENT_ACTIVITY, UPDATE_MY_PROFILE, CONTACT_TALENT, EDIT_JOB, DUPLICATE_JOB, EDIT_CANDIDATE, CUSTOM. The legacy stubs WEB_SEARCH, CALENDAR_CHECK, EMAIL_DRAFT exist in the enum but are not in the dispatch registry — agents shouldn't enable them. |
maxMessagesPerMinute | Int | 20 | Rate limit |
maxTokensPerDay | Int | 100000 | Daily token budget |
maxContextMessages | Int | 20 | Context window size |
The 27+ system tool types are fully implemented (see the Tool System section in AI Agent System for the exhaustive list with handlers). The CUSTOM type is reserved for future company-specific tools.
Update / Delete Agent
mutation UpdateAgent($id: String!, $companyId: String!, $input: UpdateAgentInput!) {
updateAgent(id: $id, companyId: $companyId, input: $input) {
success
message
agent { id name status updatedAt }
}
}
mutation DeleteAgent($id: String!, $companyId: String!) {
deleteAgent(id: $id, companyId: $companyId) { success message }
}
Query Agents
query ListAgents($companyId: String, $userId: String, $type: AgentType, $status: AgentStatus, $limit: Int, $offset: Int) {
agents(companyId: $companyId, userId: $userId, type: $type, status: $status, limit: $limit, offset: $offset) {
agents { id companyId name type status ragEnabled enabledTools totalConversations }
total limit offset
}
}
query GetAgent($id: String!, $companyId: String) {
agent(id: $id, companyId: $companyId) {
id name type status description
llmModel temperature maxTokens
ragEnabled ragTopK ragMinScore
enabledTools maxContextMessages
maxMessagesPerMinute maxTokensPerDay
totalTokensUsed totalConversations totalMessages
createdAt updatedAt
}
}
Conversation Management
Start Conversation
mutation StartConversation($input: StartConversationInput!) {
startConversation(input: $input) {
success
conversation { id agentId userId title status createdAt }
}
}
Variables:
{
"input": {
"agentId": "agent-uuid",
"userId": "user-uuid",
"companyId": "company-uuid",
"title": "Finding Python developers",
"taskCode": "recruitment_chat"
}
}
companyId is optional — omit it for job seeker conversations. The optional taskCode links the conversation to an AgentTaskConfig which can override the system prompt, temperature, RAG filters, and enabled tools for specific workflows.
If no title is provided, the system auto-generates one from the first user message (truncated to 80 characters).
List and Retrieve Conversations
query ListConversations($companyId: String, $agentId: String, $userId: String, $status: AgentConversationStatus) {
conversations(companyId: $companyId, agentId: $agentId, userId: $userId, status: $status, limit: 20) {
conversations { id title status messageCount lastMessageAt }
total
}
}
query GetMessages($conversationId: String!, $limit: Int, $before: DateTime) {
conversationMessages(conversationId: $conversationId, limit: $limit, before: $before) {
id role content status toolCalls inputTokens outputTokens ragContext createdAt
}
}
The conversations query requires at least one of agentId or userId. companyId is optional — when omitted, conversations are filtered by userId only (job seeker mode). If neither agentId nor userId is provided, an empty list is returned.
Archive and Rate
mutation ArchiveConversation($id: String!) {
archiveConversation(id: $id) { success conversation { id status } }
}
mutation RateConversation($id: String!, $rating: Int!, $feedback: String) {
rateConversation(id: $id, rating: $rating, feedback: $feedback) {
success conversation { id userRating userFeedback }
}
}
Task Configuration
Task configs allow a single agent to be optimized for different workflows.
Create Task Config
mutation CreateTaskConfig($input: CreateTaskConfigInput!) {
createTaskConfig(input: $input) {
success
taskConfig { id taskName taskCode isActive }
}
}
| Field | Type | Description |
|---|---|---|
agentId | String! | Parent agent |
taskName | String! | Display name |
taskCode | String! | Unique code per agent (e.g., recruitment_chat) |
systemPrompt | String | Override agent's system prompt |
temperature / maxTokens | Float / Int | Override LLM parameters |
ragEnabled / ragTopK / ragMinScore | Boolean / Int / Float | Override RAG settings |
ragDocumentTags | [String] | Filter RAG search to specific document tags |
enabledTools | [AgentToolType] | Override enabled tools |
fewShotExamples | JSON | Few-shot examples for better task performance |
outputFormat | String | Output format constraint (e.g., json, markdown) |
outputSchema | JSON | JSON schema for structured output |
maxResponseLength | Int | Maximum response length |
requiredFields | [String] | Fields that must appear in the response |
forbiddenTopics | [String] | Topics the agent should avoid |
Query Task Configs
query GetTaskConfigs($agentId: String!) {
agentTaskConfigs(agentId: $agentId) {
id taskName taskCode isDefault isActive
systemPrompt temperature maxTokens
ragEnabled ragTopK ragMinScore ragDocumentTags
enabledTools fewShotExamples
outputFormat outputSchema maxResponseLength
requiredFields forbiddenTopics
}
}
Document Management (RAG)
Documents uploaded to an agent are processed through a pipeline: text extraction, chunking (recursive/token/markdown strategies), embedding (Amazon Titan Embed v2, 1024 dimensions), and indexing in pgvector. The Document Worker polls every 30 seconds for new documents (5 per batch, max 3 retries on failure).
Upload Document
mutation UploadDocument($input: UploadDocumentInput!, $uploadedById: String!) {
uploadAgentDocument(input: $input, uploadedById: $uploadedById) {
success document { id filename status category }
}
}
| Field | Type | Description |
|---|---|---|
agentId | String! | Target agent |
companyId | String | Company scope (optional for job seeker agents) |
filename | String! | Original filename |
mimeType | String! | MIME type (PDF, DOCX, images) |
fileUrl | String! | S3 or presigned URL |
bucket | String! | S3 bucket name |
key | String! | S3 object key |
category | AgentDocumentCategory | POLICY, PROCEDURE, KNOWLEDGE_BASE, FAQ, TEMPLATE, REFERENCE, TRAINING, OTHER |
tags | [String] | Tags for RAG filtering |
includeInRag | Boolean | Include in RAG search (default: true) |
ragPriority | Int | Priority ranking (higher = more relevant) |
chunkSize | Int | Chunk size in characters (default: 512) |
chunkOverlap | Int | Overlap between chunks (default: 50) |
Document status progression: PENDING → PROCESSING → CHUNKING → EMBEDDING → COMPLETED (or FAILED).
Query and Manage Documents
query GetDocuments($agentId: String!, $category: AgentDocumentCategory, $status: AgentDocumentStatus, $includeInRag: Boolean) {
agentDocuments(agentId: $agentId, category: $category, status: $status, includeInRag: $includeInRag, limit: 20) {
documents { id filename status totalChunks extractedTextLength processingTimeMs errorMessage }
total
}
}
query GetDocument($id: String!) {
agentDocument(id: $id) {
id filename mimeType sizeBytes status category tags
includeInRag ragPriority chunkSize chunkOverlap totalChunks
extractedTextLength processingTimeMs errorMessage
createdAt updatedAt
}
}
mutation DeleteDocument($id: String!) { deleteAgentDocument(id: $id) { success } }
mutation EnableRag($id: String!) { enableDocumentRag(id: $id) { success document { id includeInRag } } }
mutation DisableRag($id: String!) { disableDocumentRag(id: $id) { success document { id includeInRag } } }
mutation Reprocess($id: String!) { reprocessDocument(id: $id) { success document { id status } } }
Chat Subscription (Streaming)
The agentChat subscription is the primary way to interact with agents. It streams responses in real-time over WebSocket.
subscription AgentChat($input: AgentChatInput!) {
agentChat(input: $input) {
... on ChatProcessing { id status message }
... on ChatContextLoaded { id contextChunks contextTokens }
... on ChatChunk { id delta chunkIndex }
... on ChatToolCall { id toolName toolType inputData }
... on ChatToolResult { id toolCallId output success executionTimeMs }
... on ChatComplete { id messageId content inputTokens outputTokens totalTimeMs }
... on ChatError { id code message recoveryHint }
}
}
AgentChatInput fields:
| Field | Type | Description |
|---|---|---|
conversationId | String! | Conversation UUID |
message | String! | User message text |
attachments | [AgentChatAttachmentInput] | File attachments (S3 key, bucket, filename, mimeType, sizeBytes) |
forcedTools | [String] | Tool names to force (overrides automatic selection) |
pageContext | String | Current page context key (e.g. talents, js_jobs, job_detail) |
entityId | String | UUID of entity being viewed on detail pages |
Example input: { "input": { "conversationId": "uuid", "message": "your question", "pageContext": "talent_detail", "entityId": "talent-uuid" } }
When pageContext is provided, the system injects page-specific guidance into the LLM system prompt and reorders tools to prioritize those relevant to the current page. See Page Context for the full mapping.
Timeout: 10 minutes (2x the default subscription timeout, to accommodate LLM calls + tool execution).
Event Sequence
A typical streaming response follows this sequence:
| Order | Event | Description |
|---|---|---|
| 1 | ChatProcessing | Request acknowledged, processing started |
| 2 | ChatContextLoaded | RAG context retrieved (only if RAG enabled and results found) |
| 3 | ChatChunk (repeated) | Streaming text deltas from the LLM |
| 3a | ChatToolCall | Agent invoked a tool (interrupts chunk stream) |
| 3b | ChatToolResult | Tool execution result returned |
| 3c | ChatChunk (resumed) | LLM continues with tool results in context |
| 4 | ChatComplete | Full response with final content and token metrics |
| -- | ChatError | Replaces any event on failure |
Error Codes
| Code | Description | Recovery |
|---|---|---|
CONVERSATION_NOT_FOUND | Invalid conversation ID | Create a new conversation |
CONVERSATION_INACTIVE | Conversation archived or deleted | Start a new conversation |
UNAUTHORIZED | No access to this conversation | Check JWT and company scope |
RATE_LIMITED | Too many messages per minute | Wait and retry |
TOKEN_LIMIT_EXCEEDED | Daily token budget exhausted | Wait for reset or increase limit |
CHAT_ERROR | LLM processing error | Retry the message |
TOOL_ERROR | Tool execution failed | Check tool configuration |
INSUFFICIENT_CREDITS | Credit balance too low | Purchase more credits |
SUBSCRIPTION_INACTIVE | No active subscription | Activate your subscription |
PLAN_LIMIT_EXCEEDED | Plan limit reached | Upgrade your plan |
Tool System
Agents can invoke tools during conversations. Tool calls appear as ChatToolCall / ChatToolResult events in the subscription stream.
Implemented Tools (core)
This table lists the most-used tools. The full registry has 27+ tools (self-service, write operations, analytics, meeting-insight lookups). See AI Agent System — Tool System for the exhaustive list and ViewMode-Aware Behaviour for the 15-tool job-seeker subset.
| Tool | Function | Description | Key Parameters |
|---|---|---|---|
RAG_SEARCH | rag_search | Search company knowledge base via hybrid vector + full-text search | query (required), top_k (default 5), document_tags |
DATABASE_QUERY | database_query | Execute read-only SQL against whitelisted tables | query (required, SELECT only), template_name, params |
TALENT_SEARCH | search_talents | Search talent pool by skills, experience, location | skills, experience_years, location, availability, limit (1-50) |
JOB_SEARCH | search_jobs | Search active job openings by criteria | keywords, job_type (FREELANCE_GIG/DIRECT_HIRE/TRY_HIRE), location, limit (1-50) |
COMPANY_INFO | get_company_info | Get company details, members, and stats. Recruiters default to their own company; job seekers must pass company_id or company_name to research a specific employer. Private stats (talent/candidate/interview counts, members list) are suppressed for cross-company and job-seeker lookups. | company_id, company_name, include_members, include_stats |
RECOMMEND_JOBS_FOR_TALENT | recommend_jobs_for_talent | Find best-matching jobs via pre-computed UserJobMatchScore. Recruiter mode: pass talent_id (or rely on the talent_detail page entity). Job-seeker mode: call with NO arguments — resolves the target from the caller's own user_id and returns matches across all active platform jobs with company_name per result. | talent_id (optional), min_score, limit |
CANDIDATE_SEARCH | search_candidates | Search candidates across pipelines by job, status, keywords | query (required), status, limit (1-50) |
PIPELINE_OVERVIEW | get_pipeline_overview | View candidate stage breakdown for a specific job | jobId (optional — if omitted, LLM asks the user) |
INTERVIEW_SCHEDULE | get_interviews | List interviews with scores, status, and job context | status, upcoming |
MEETING_LIST | get_meetings | View meetings with insights and transcription status | status, upcoming |
CREATE_TALENT | create_talent | Create a new talent in the company's talent pool | name (required), email, phone, skills |
CV_EXTRACTION | extract_cv | Parse CV document to structured data via full OCR pipeline | file_url (required). 120s timeout |
JOB_PARSING | parse_job | Parse job description from text or file URL | text or file_url (one required). 90s timeout |
Tool Security
- Limit bounds: All search limits clamped to
max(1, min(limit, 50)) - Skills cap: Max 20 skills per search query (prevents SQL explosion)
- SQL safety: DATABASE_QUERY is SELECT-only with sanitized error messages (no schema leakage)
- Timeouts: CV_EXTRACTION (120s), JOB_PARSING (90s) via
asyncio.wait_for() - Output truncation: Tool output truncated to 50KB before database storage
- Email validation: CREATE_TALENT validates email format via regex
- Multi-tenant / ViewMode gate: All company queries are scoped by
company_id. When the caller has no company context (job-seeker mode),execute_toolrefuses any tool that is not on theJOB_SEEKER_ALLOWED_TOOL_TYPESallowlist with a targeted redirect message, andget_enabled_toolsfilters the advertised list down to the allowlist so the LLM never sees blocked tools. See ViewMode-Aware Behaviour for the full allowlist and the handlers with job-seeker branches.
Database Access Configuration
Control what database tables and operations an agent can use:
mutation UpdateDatabaseConfig($agentId: String!, $input: UpdateDatabaseConfigInput!) {
updateAgentDatabaseConfig(agentId: $agentId, input: $input) {
success
config { id accessLevel allowedTables deniedTables maskPii hideSalaryData }
}
}
query GetDatabaseConfig($agentId: String!) {
agentDatabaseConfig(agentId: $agentId) {
id accessLevel maxRowsPerQuery queryTimeoutMs
allowJoins allowAggregations allowSubqueries
allowedTables deniedTables maskPii hideSalaryData
createdAt updatedAt
}
}
| Field | Type | Default | Description |
|---|---|---|---|
accessLevel | DatabaseAccessLevel | READ_ONLY | NONE, READ_ONLY, READ_WRITE |
allowedTables | [String] | [] | Whitelist of table names |
deniedTables | [String] | [] | Blacklist of table names (takes priority) |
maxRowsPerQuery | Int | 100 | Max rows returned per query |
queryTimeoutMs | Int | 5000 | Query timeout |
allowJoins | Boolean | true | Allow JOIN operations |
allowAggregations | Boolean | true | Allow GROUP BY / aggregations |
allowSubqueries | Boolean | false | Allow subqueries |
maskPii | Boolean | true | Mask personally identifiable information |
hideSalaryData | Boolean | true | Hide salary fields |
Voice Integration
The voice WebSocket provides bidirectional speech-to-text (STT) and text-to-speech (TTS) using Amazon Nova Sonic.
Endpoint: wss://ai.aiqlick.com/voice/ws/{conversation_id}
Audio format: PCM, 16kHz sample rate, 16-bit depth, mono channel.
Protocol
Client to server:
| Message | Format | Description |
|---|---|---|
| Start session | {"type":"start","language":"en-US","voice_id":"tiffany"} | Initialize voice session |
| Audio data | Binary PCM | Raw audio chunks |
| Synthesize | {"type":"synthesize","text":"Hello"} | Request TTS |
| End session | {"type":"end"} | Close session |
Server to client:
| Message | Description |
|---|---|
session_started | Session initialized with session_id |
transcript | STT result with text, is_partial, confidence |
audio | TTS result with base64 PCM data |
complete | Session ended with full transcript |
error | Error with code and message |
Supported languages: en-US, en-GB, en-IN, en-AU, es-ES, de-DE, fr-FR, it-IT, pt-BR, hi-IN. Voices: tiffany (female), matthew (male) -- both polyglot.
Swedish (sv-SE) is NOT supported by Nova Sonic despite being the platform's primary market.
Token Usage and Monitoring
query GetTokenUsage($companyId: String!, $periodStart: DateTime!, $periodEnd: DateTime!, $agentId: String) {
agentTokenUsage(companyId: $companyId, periodStart: $periodStart, periodEnd: $periodEnd, agentId: $agentId) {
inputTokens outputTokens totalTokens estimatedCostCents
}
}
query GetActivities($companyId: String!, $agentId: String, $activityType: AgentActivityType) {
agentActivities(companyId: $companyId, agentId: $agentId, activityType: $activityType, limit: 50) {
id activityType description resourceType resourceId metadata createdAt
}
}
Frontend Integration
Apollo Client Setup
import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
const httpLink = new HttpLink({ uri: 'https://ai.aiqlick.com/graphql' });
const wsLink = new GraphQLWsLink(
createClient({
url: 'wss://ai.aiqlick.com/graphql',
connectionParams: { authorization: `Bearer ${getToken()}` },
})
);
const splitLink = split(
({ query }) => {
const def = getMainDefinition(query);
return def.kind === 'OperationDefinition' && def.operation === 'subscription';
},
wsLink,
httpLink
);
export const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache() });
React Chat Hook
import { useState, useCallback, useRef } from 'react';
import { createClient, Client } from 'graphql-ws';
export function useAgentChat({ conversationId, onError }) {
const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const clientRef = useRef<Client | null>(null);
const sendMessage = useCallback(async (content: string) => {
setMessages(prev => [...prev, { id: `user-${Date.now()}`, role: 'user', content }]);
const assistantId = `assistant-${Date.now()}`;
setMessages(prev => [...prev, { id: assistantId, role: 'assistant', content: '', isStreaming: true }]);
setIsLoading(true);
// Phase 3.7 cutover: route via the NestJS AI gateway. The frontend
// never opens a direct WS to background-tasks. `subscribeAiOp`
// (lib/ai/aiGateway.ts) wraps the standard Apollo client.
subscribeAiOp(apollo, 'agentChat', { conversationId, message: content }, {
onUpdate: (update: AgentChatEvent) => {
if (update?.__typename === 'ChatChunk') {
setMessages(prev => prev.map(m =>
m.id === assistantId ? { ...m, content: m.content + update.delta } : m
));
} else if (update?.__typename === 'ChatComplete') {
setMessages(prev => prev.map(m =>
m.id === assistantId ? { ...m, isStreaming: false, inputTokens: update.inputTokens } : m
));
setIsLoading(false);
} else if (update?.__typename === 'ChatError') {
onError?.(update);
setIsLoading(false);
}
},
onError: (err) => { onError?.(err); setIsLoading(false); },
onComplete: () => setIsLoading(false),
},
);
}, [conversationId]);
return { messages, isLoading, sendMessage };
}
The frontend uses one Apollo client pointed at the backend (NEXT_PUBLIC_API_URL / NEXT_PUBLIC_GRAPHQL_WS_URL). All AI traffic — including agentChat — goes through the NestJS AI gateway over subscribeAi. The legacy NEXT_PUBLIC_AI_* env vars were removed in the Phase 3.7 cutover.
Rate Limits
| Limit | Default | Scope |
|---|---|---|
| Messages per minute | 20 | Per agent (enforced in conversation service) |
| Tokens per day | 100,000 | Per agent (checked against AgentTokenUsage) |
| Context messages | 20 | Per conversation |
| DB rows per query | 100 | Per query (from AgentDatabaseConfig) |
| DB query timeout | 5,000ms | Per query |
| Bedrock API timeout | 300s | Per LLM call |
| Subscription timeout | 600s | Per chat session (10 minutes) |