Skip to main content

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

EnvironmentHTTPWebSocketVoice WebSocket
Productionhttps://ai.aiqlick.com/graphqlwss://ai.aiqlick.com/graphqlwss://ai.aiqlick.com/voice/ws/{conversation_id}
Developmenthttps://ai-dev.aiqlick.com/graphqlwss://ai-dev.aiqlick.com/graphqlwss://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:

FieldTypeDefaultDescription
companyIdStringnullCompany UUID (required for employers, omit for job seekers)
nameString!--Display name (unique per company or per user)
typeAgentType!--RECRUITMENT_ASSISTANT, TALENT_SEARCH, JOB_MATCHING, INTERVIEW_PREP, DATA_ANALYST, CUSTOM
descriptionStringnullAgent description
llmModelStringanthropic.claude-3-sonnet-20240229-v1:0Bedrock model ID
temperatureFloat0.7LLM temperature
maxTokensInt4096Max output tokens
systemPromptStringnullCustom system prompt
ragEnabledBooleanfalseEnable RAG document search
ragTopKInt5Number of RAG chunks to retrieve
ragMinScoreFloat0.7Minimum 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.
maxMessagesPerMinuteInt20Rate limit
maxTokensPerDayInt100000Daily token budget
maxContextMessagesInt20Context window size
info

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.

tip

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
}
}
note

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 }
}
}
FieldTypeDescription
agentIdString!Parent agent
taskNameString!Display name
taskCodeString!Unique code per agent (e.g., recruitment_chat)
systemPromptStringOverride agent's system prompt
temperature / maxTokensFloat / IntOverride LLM parameters
ragEnabled / ragTopK / ragMinScoreBoolean / Int / FloatOverride RAG settings
ragDocumentTags[String]Filter RAG search to specific document tags
enabledTools[AgentToolType]Override enabled tools
fewShotExamplesJSONFew-shot examples for better task performance
outputFormatStringOutput format constraint (e.g., json, markdown)
outputSchemaJSONJSON schema for structured output
maxResponseLengthIntMaximum 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 }
}
}
FieldTypeDescription
agentIdString!Target agent
companyIdStringCompany scope (optional for job seeker agents)
filenameString!Original filename
mimeTypeString!MIME type (PDF, DOCX, images)
fileUrlString!S3 or presigned URL
bucketString!S3 bucket name
keyString!S3 object key
categoryAgentDocumentCategoryPOLICY, PROCEDURE, KNOWLEDGE_BASE, FAQ, TEMPLATE, REFERENCE, TRAINING, OTHER
tags[String]Tags for RAG filtering
includeInRagBooleanInclude in RAG search (default: true)
ragPriorityIntPriority ranking (higher = more relevant)
chunkSizeIntChunk size in characters (default: 512)
chunkOverlapIntOverlap between chunks (default: 50)

Document status progression: PENDINGPROCESSINGCHUNKINGEMBEDDINGCOMPLETED (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:

FieldTypeDescription
conversationIdString!Conversation UUID
messageString!User message text
attachments[AgentChatAttachmentInput]File attachments (S3 key, bucket, filename, mimeType, sizeBytes)
forcedTools[String]Tool names to force (overrides automatic selection)
pageContextStringCurrent page context key (e.g. talents, js_jobs, job_detail)
entityIdStringUUID 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:

OrderEventDescription
1ChatProcessingRequest acknowledged, processing started
2ChatContextLoadedRAG context retrieved (only if RAG enabled and results found)
3ChatChunk (repeated)Streaming text deltas from the LLM
3aChatToolCallAgent invoked a tool (interrupts chunk stream)
3bChatToolResultTool execution result returned
3cChatChunk (resumed)LLM continues with tool results in context
4ChatCompleteFull response with final content and token metrics
--ChatErrorReplaces any event on failure

Error Codes

CodeDescriptionRecovery
CONVERSATION_NOT_FOUNDInvalid conversation IDCreate a new conversation
CONVERSATION_INACTIVEConversation archived or deletedStart a new conversation
UNAUTHORIZEDNo access to this conversationCheck JWT and company scope
RATE_LIMITEDToo many messages per minuteWait and retry
TOKEN_LIMIT_EXCEEDEDDaily token budget exhaustedWait for reset or increase limit
CHAT_ERRORLLM processing errorRetry the message
TOOL_ERRORTool execution failedCheck tool configuration
INSUFFICIENT_CREDITSCredit balance too lowPurchase more credits
SUBSCRIPTION_INACTIVENo active subscriptionActivate your subscription
PLAN_LIMIT_EXCEEDEDPlan limit reachedUpgrade your plan

Tool System

Agents can invoke tools during conversations. Tool calls appear as ChatToolCall / ChatToolResult events in the subscription stream.

Implemented Tools (core)

note

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.

ToolFunctionDescriptionKey Parameters
RAG_SEARCHrag_searchSearch company knowledge base via hybrid vector + full-text searchquery (required), top_k (default 5), document_tags
DATABASE_QUERYdatabase_queryExecute read-only SQL against whitelisted tablesquery (required, SELECT only), template_name, params
TALENT_SEARCHsearch_talentsSearch talent pool by skills, experience, locationskills, experience_years, location, availability, limit (1-50)
JOB_SEARCHsearch_jobsSearch active job openings by criteriakeywords, job_type (FREELANCE_GIG/DIRECT_HIRE/TRY_HIRE), location, limit (1-50)
COMPANY_INFOget_company_infoGet 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_TALENTrecommend_jobs_for_talentFind 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_SEARCHsearch_candidatesSearch candidates across pipelines by job, status, keywordsquery (required), status, limit (1-50)
PIPELINE_OVERVIEWget_pipeline_overviewView candidate stage breakdown for a specific jobjobId (optional — if omitted, LLM asks the user)
INTERVIEW_SCHEDULEget_interviewsList interviews with scores, status, and job contextstatus, upcoming
MEETING_LISTget_meetingsView meetings with insights and transcription statusstatus, upcoming
CREATE_TALENTcreate_talentCreate a new talent in the company's talent poolname (required), email, phone, skills
CV_EXTRACTIONextract_cvParse CV document to structured data via full OCR pipelinefile_url (required). 120s timeout
JOB_PARSINGparse_jobParse job description from text or file URLtext 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_tool refuses any tool that is not on the JOB_SEEKER_ALLOWED_TOOL_TYPES allowlist with a targeted redirect message, and get_enabled_tools filters 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
}
}
FieldTypeDefaultDescription
accessLevelDatabaseAccessLevelREAD_ONLYNONE, READ_ONLY, READ_WRITE
allowedTables[String][]Whitelist of table names
deniedTables[String][]Blacklist of table names (takes priority)
maxRowsPerQueryInt100Max rows returned per query
queryTimeoutMsInt5000Query timeout
allowJoinsBooleantrueAllow JOIN operations
allowAggregationsBooleantrueAllow GROUP BY / aggregations
allowSubqueriesBooleanfalseAllow subqueries
maskPiiBooleantrueMask personally identifiable information
hideSalaryDataBooleantrueHide 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:

MessageFormatDescription
Start session{"type":"start","language":"en-US","voice_id":"tiffany"}Initialize voice session
Audio dataBinary PCMRaw audio chunks
Synthesize{"type":"synthesize","text":"Hello"}Request TTS
End session{"type":"end"}Close session

Server to client:

MessageDescription
session_startedSession initialized with session_id
transcriptSTT result with text, is_partial, confidence
audioTTS result with base64 PCM data
completeSession ended with full transcript
errorError 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.

warning

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 };
}
info

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

LimitDefaultScope
Messages per minute20Per agent (enforced in conversation service)
Tokens per day100,000Per agent (checked against AgentTokenUsage)
Context messages20Per conversation
DB rows per query100Per query (from AgentDatabaseConfig)
DB query timeout5,000msPer query
Bedrock API timeout300sPer LLM call
Subscription timeout600sPer chat session (10 minutes)