Skip to content

Connect JOIN to AI Agents: Automate the Full Recruitment Lifecycle

Yuvraj Muley Yuvraj Muley 10 min read AI & Agents
TrutoFor teams building AI agents

Give your AI agent JOIN tools.

Connect JOIN to AI agents using Truto's /tools API to automate your recruitment lifecycle. Learn to bind robust LLM tools, handle JOIN API quirks, and implement resilient rate-limit backoff.

In this guide

  1. 01Fetch JOIN tool schemas from Truto
  2. 02Implement rate limit handling
  3. 03Bind tools to your agent framework
  4. 04Execute recruitment workflows
Use JOIN in your own ChatGPT or Claude. Elaichi, from the team behind Truto, free for 14 days. Try Elaichi

The guide

Learn how to connect JOIN to AI agents using Truto's /tools endpoint. Step-by-step guide to automating candidate workflows and ATS operations.

You want to connect JOIN to an AI agent so your system can autonomously draft job descriptions, screen applications, download resumes, and manage your entire talent pipeline. Here is exactly how to do it using Truto's /tools endpoint and SDK, bypassing the need to build and maintain a custom JOIN integration from scratch.

Giving a Large Language Model (LLM) read and write access to your applicant tracking system (ATS) is an engineering headache. You either spend weeks building, hosting, and maintaining a custom connector that handles multipart file downloads and complex candidate schemas, or you use a managed infrastructure layer that handles the boilerplate for you. If your team uses ChatGPT, check out our guide on connecting JOIN to ChatGPT, or if you are building on Anthropic's models, read our guide on connecting JOIN to Claude. For developers building custom autonomous workflows, you need a programmatic way to fetch these tools and bind them to your agent framework.

This guide breaks down exactly how to fetch AI-ready tools for JOIN, bind them natively to an LLM using frameworks like LangChain, LangGraph, CrewAI, or the Vercel AI SDK, and execute complex recruitment operations workflows. For a broader look at the architecture behind this approach, refer to our research on architecting AI agents and the Saas integration bottleneck.

The Engineering Reality of the JOIN API

Giving an LLM access to external recruitment data sounds simple in a local prototype. You write a Node.js function that makes a fetch request and wrap it in an @tool decorator. In production against complex ATS platforms, this approach collapses.

JOIN's API introduces several specific integration challenges that break standard REST assumptions. If you hardcode these interactions into your agent, you will spend your sprints writing defensive integration code instead of improving your model's reasoning and candidate evaluation logic.

The Application Filtering Trap

When an LLM attempts to find rejected candidates, it naturally guesses standard REST patterns like ?status=rejected or ?state=archived. JOIN's application filtering is highly specific and actively deprecating old patterns. The state filter is deprecated and cannot be combined with newer filters. To filter applications properly, the agent must use a combination of hiringState and stageType. Furthermore, stageType strictly requires hiringState to be present. If an agent hallucinates a filter combination, the API rejects the call. Truto's tool schema enforces the correct dependencies so the LLM knows exactly which parameters are mutually exclusive.

The Job Content Override Requirement

When querying for job details, JOIN defaults to returning only basic metadata. The actual job description, salary, contact person, and attachments are missing unless a specific content=true flag is passed. The JOIN API documentation does not always explicitly document this for single-job endpoints, leading to null pointer exceptions when agents attempt to parse missing descriptions. Truto handles this quirk by automatically appending status=ONLINE,OFFLINE,ARCHIVED and content=true by default when the agent lists jobs, guaranteeing the LLM has the full context required to evaluate the role.

The Signature-Based File Download Quirk

Retrieving candidate resumes is critical for any AI recruitment workflow. However, you cannot simply pass an attachment ID to a GET endpoint. JOIN uses a signature-based security model for files. Every attachment URL returned in the application object contains a temporary sig query parameter. To download the actual file, the caller must extract this sig token and pass it along with the attachment_id and external_file_name. Additionally, the endpoint returns raw binary data (application/pdf), not a JSON object. Standard LLM tool wrappers crash when they encounter binary streams instead of JSON. Truto provides dedicated tools that handle the signature extraction and binary handoff safely.

Why a Unified Tool Layer Matters for Agent Safety

Before writing a line of integration code, decide what layer your agent talks to. This choice determines how safe and deterministic your production system will be.

Direct API tools - writing one bespoke function per raw JOIN endpoint - look convenient initially, but they push provider-specific quirks directly into the LLM's context window. The model has to remember that JOIN requires hiringState instead of state, that file downloads need a sig parameter, and that job updates require nested category IDs. Every one of those quirks is a hallucination waiting to happen.

Truto collapses this complexity. Your agent sees stable, declarative tools. That gives you concrete safety wins:

  1. Deterministic input validation. Every tool provided by the Truto /tools endpoint has a strict JSON schema. Invalid arguments (like sending a deprecated state filter) are rejected by the schema validator before they hit the JOIN API. A broken tool call fails fast, allowing the LLM to correct itself instantly.
  2. Smaller attack surface for hallucination. The LLM only ever chooses from clearly defined function names. It never invents query parameter fragments or pagination cursors.
  3. Centralized authentication. Your agent code never touches JOIN API keys. It uses a single Truto bearer token, and Truto manages the underlying integrated account identity.

Hero Tools for JOIN Automation

Truto exposes JOIN's endpoints as highly structured tools optimized for LLM function calling. Here are the core tools you will use to build autonomous recruitment workflows.

list_all_join_jobs

Retrieves a complete list of jobs in the JOIN workspace. Truto overrides JOIN's default behavior by automatically injecting content=true and querying across all statuses (ONLINE, OFFLINE, ARCHIVED), ensuring the agent receives the full job description, salary details, and requirements needed for context.

"Fetch all of our currently active engineering job postings and extract their core technical requirements."

list_all_join_applications

Lists applications with strict schema-enforced filters for job ID, integration external ID, hiring state, and stage type. The schema explicitly guides the LLM to use hiringState rather than the deprecated state parameter.

"Find all applications for the Senior DevOps role that are currently in the 'Interview' stage type."

get_single_join_candidate_by_id

Fetches the complete profile of a candidate, including their professional links, tags, consent status, and embedded candidate notes. This is the primary tool an agent uses to build a comprehensive profile before drafting interview questions.

"Get the full profile and notes for candidate ID 847291 to help me prepare a technical screening script."

create_a_join_job

Creates a new job in JOIN. The schema enforces required fields like title, categoryId, language, employmentTypeId, and officeId. New jobs are automatically set to active and multiposted to connected job boards.

"Draft a new job posting for a Remote Product Manager based in the Berlin office, using employment type ID 3, and post it to JOIN."

create_a_join_note

Posts an internal note to a candidate's profile. This is essential for agents acting as automated screeners, allowing them to summarize resumes or log interaction transcripts directly onto the candidate record.

"Add a private note to candidate ID 55219 summarizing their experience with Kubernetes and their expected salary range."

join_attachments_download

Downloads the raw binary file of a candidate's attachment (usually a resume or cover letter). The tool schema instructs the LLM to pass the required sig signature string, attachment_id, and external_file_name to bypass JOIN's security constraints safely.

"Download the PDF resume for candidate ID 33912 using the signature token from their application record."

To view the complete inventory of JOIN tools, payload schemas, and required parameters, visit the JOIN integration page.

Workflows in Action

AI agents excel when they chain multiple specialized tool calls together to complete a business objective. Here is how these tools look in production.

Scenario 1: Autonomous Resume Screening and Feedback

When a new batch of candidates applies for an open role, recruiters waste hours manually opening PDFs and writing summary notes. An agent can completely automate the top-of-funnel screening process.

"Find all new applications for the Backend Developer job. Download their resumes, evaluate their experience against our requirement for 5 years of Node.js, and leave a summary note on each candidate's profile with a recommendation."

  1. The agent calls list_all_join_applications filtered by the target job_id and the initial pipeline stage.
  2. For each application, the agent extracts the sig token and calls join_attachments_download to fetch the resume file.
  3. The agent processes the document text internally (using its base LLM capabilities or an OCR tool).
  4. The agent formulates a screening summary.
  5. The agent calls create_a_join_note to append the evaluation directly to the candidate's JOIN record.

The recruiter logs into JOIN and sees a beautifully formatted, objective summary attached to every new applicant, ready for human review.

Scenario 2: Pipeline Maintenance and Job Archiving

Keeping the ATS clean is a tedious administrative task. Agents can enforce pipeline hygiene by archiving stale jobs and standardizing applicant tags.

"Find any job postings in the Sales category that haven't been updated in 90 days. Archive them to stop multiposting, and update all active candidates in those pipelines with the tag 'Archived-Role-2026'."

  1. The agent calls list_all_join_jobs and inspects the lastUpdatedAt and categoryId fields.
  2. For matches older than 90 days, the agent calls join_jobs_bulk_change_status passing the job_id and setting the status to ARCHIVED.
  3. The agent then calls list_all_join_applications for those specific jobs.
  4. The agent iterates through the candidates and calls update_a_join_candidate_by_id, injecting the new tag array to reflect their updated status.

The talent acquisition team maintains a pristine ATS without manual data entry.

Building Multi-Step Workflows

To orchestrate these tools, you need to connect your agent framework to Truto's /tools endpoint. Truto returns all active methods on an integration as an array of JSON schemas formatted explicitly for LLM consumption. You simply pass these to your framework's .bindTools() method.

The Architecture

The pattern is simple but resilient. The LLM requests tools from Truto, binds them to its reasoning engine, and enters a standard evaluation loop. When the LLM decides to take action, it outputs a structured function call. Your runtime executes that call against Truto's Proxy API, which handles the authentication mapping to JOIN.

sequenceDiagram
    participant LLM as Agent Framework
    participant Truto as Truto API
    participant JOIN as JOIN API

    LLM ->> Truto: GET /integrated-account/<id>/tools
    Truto -->> LLM: Returns tool array (JSON schemas)
    LLM ->> LLM: Initialize agent with .bindTools()
    Note over LLM: Agent loop begins<br>analyzes user prompt
    LLM ->> Truto: Execute list_all_join_jobs
    Truto ->> JOIN: GET /api/v1/jobs?content=true
    JOIN -->> Truto: 200 OK (Job data)
    Truto -->> LLM: Normalized JSON response
    Note over LLM: Agent formulates<br>final answer

Handling Rate Limits and Execution

A critical engineering reality when interacting with third-party systems is API throttling. Truto does not retry, throttle, or apply backoff on rate limit errors. If your agent makes too many calls and the upstream JOIN API returns an HTTP 429 Too Many Requests, Truto passes that 429 directly back to your caller.

To standardize this for developers, Truto normalizes the upstream rate limit information into IETF spec standard headers: ratelimit-limit, ratelimit-remaining, and ratelimit-reset. It is the caller's responsibility to inspect these headers and implement exponential backoff.

Here is a complete, framework-agnostic TypeScript example using @langchain/openai showing how to fetch tools, bind them to an agent, and implement a safe execution wrapper that respects Truto's rate limit headers.

import { ChatOpenAI } from "@langchain/openai";
import { AgentExecutor, createOpenAIToolsAgent } from "langchain/agents";
import { ChatPromptTemplate } from "@langchain/core/prompts";
 
// 1. A resilient fetch wrapper that handles Truto's 429 rate limit errors
async function fetchWithBackoff(url: string, options: RequestInit, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);
    
    // If we hit a 429, we must back off using Truto's normalized headers
    if (response.status === 429) {
      const resetTime = response.headers.get('ratelimit-reset');
      const retryAfter = resetTime 
        ? Math.max(0, parseInt(resetTime) * 1000 - Date.now()) 
        : Math.pow(2, attempt) * 1000;
 
      console.warn(`Rate limited by upstream API. Retrying in ${retryAfter}ms...`);
      await new Promise(resolve => setTimeout(resolve, retryAfter));
      continue;
    }
    
    if (!response.ok) {
      throw new Error(`API Error: ${response.status} - ${await response.text()}`);
    }
    
    return response.json();
  }
  throw new Error("Max retries exceeded");
}
 
async function runJoinAgent(prompt: string, integratedAccountId: string, trutoApiKey: string) {
  // 2. Fetch the JOIN tool definitions directly from Truto
  const toolsResponse = await fetchWithBackoff(
    `https://api.truto.one/integrated-account/${integratedAccountId}/tools`,
    {
      headers: {
        'Authorization': `Bearer ${trutoApiKey}`
      }
    }
  );
 
  // Convert Truto's schema definitions into LangChain compatible tools
  // (In production, use TrutoToolManager from truto-langchainjs-toolset)
  const tools = toolsResponse.tools.map((t: any) => ({
    name: t.name,
    description: t.description,
    schema: t.schema,
    // Custom execution function mapping to Truto's proxy endpoints
    func: async (args: any) => {
        const res = await fetchWithBackoff(
            `https://api.truto.one/integrated-account/${integratedAccountId}/proxy/${t.method}`,
            {
                method: 'POST', 
                headers: { 
                    'Authorization': `Bearer ${trutoApiKey}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(args)
            }
        );
        return JSON.stringify(res);
    }
  }));
 
  // 3. Initialize the LLM and bind the tools
  const model = new ChatOpenAI({ 
      modelName: "gpt-4o", 
      temperature: 0 
  });
  const modelWithTools = model.bindTools(tools);
 
  // 4. Create the agent prompt and executor loop
  const promptTemplate = ChatPromptTemplate.fromMessages([
    ["system", "You are an expert technical recruiter assistant. You have access to tools that interact with the JOIN ATS. Think step-by-step."],
    ["human", "{input}"],
    ["placeholder", "{agent_scratchpad}"],
  ]);
 
  const agent = await createOpenAIToolsAgent({
    llm: modelWithTools,
    tools,
    prompt: promptTemplate,
  });
 
  const agentExecutor = new AgentExecutor({
    agent,
    tools,
    maxIterations: 10,
  });
 
  // 5. Execute the workflow
  const result = await agentExecutor.invoke({ input: prompt });
  console.log("Agent response:", result.output);
}
 
// Example usage:
// runJoinAgent("List all active jobs and tell me how many candidates applied for the Engineering Manager role.", "acc_123abc", "sk_truto_...");

This architecture puts you firmly in control. Your agent framework drives the logic, Truto provides the secure translation layer, and your fetch wrapper protects the system from cascading rate limit failures.

Moving Forward

Connecting AI agents to your ATS unlocks massive operational velocity for talent acquisition teams, but writing manual integration code for JOIN is a poor use of engineering resources. By leveraging Truto's /tools endpoint, you provide your agents with a stable, schema-validated toolkit that protects against hallucinations and gracefully handles API complexity.

Instead of reading through vendor documentation to figure out which filters are deprecated and how to parse signature tokens, your team can focus on refining the AI's candidate evaluation prompts and workflow orchestration.

Two ways to put JOIN to work

Elaichifrom the team behind Truto

For you and your team

Use JOIN in ChatGPT or Claude yourself

Connect JOIN once, add Elaichi to ChatGPT or Claude, and ask. Every call is checked against your own permissions and logged.

Start free, 14 days No credit card required
Truto

For product teams

Give your agent JOIN tools

Your customers connect their own JOIN accounts. Your product gets one API and MCP tools for JOIN, through Truto.

FAQ

How does Truto handle JOIN rate limits for AI agents?
Truto does not automatically retry, throttle, or apply backoff. It passes HTTP 429 errors directly to the caller, normalizing the upstream rate limit data into standard headers (`ratelimit-limit`, `ratelimit-remaining`, `ratelimit-reset`). The caller is responsible for implementing retry logic.
Can AI agents download candidate resumes from JOIN?
Yes. Truto provides a specific tool (`join_attachments_download`) that allows the agent to extract the required `sig` token from the application data and safely download the raw binary file (e.g., application/pdf) representing the resume.
How do I ensure the agent gets the full job description from JOIN?
By default, JOIN returns only metadata for jobs unless a specific content flag is used. Truto's `list_all_join_jobs` tool automatically injects `content=true` to ensure your LLM receives the full job description required for context analysis.
Which LLM frameworks support Truto's JOIN tools?
Truto's `/tools` endpoint returns standard JSON schemas that can be used with any major agent framework, including LangChain, LangGraph, CrewAI, and the Vercel AI SDK. Truto also provides an official LangChain SDK.
J JOINAI agent tools Get a sandbox

More from our Blog