Connect Justworks to AI Agents: Sync Paystubs & Time Off Reporting
Give your AI agent Justworks tools.
Connect Justworks to AI agents to automate payroll, deductions, and time-off reporting. This guide covers API quirks, rate limits, tool calling, and multi-step workflows using Truto.
In this guide
- 01Determine Tool Layer Architecture
- 02Handle Justworks API Formatting Requirements
- 03Fetch AI-Ready Tools from Truto
- 04Bind Tools to the LLM
- 05Implement Rate Limit and Error Handling
The guide
Learn how to connect Justworks to AI agents using Truto. Discover how to safely handle payroll tools, time-off reports, and HR data with LLMs.
You want to connect Justworks to an AI agent so your system can independently fetch member data, sync paystubs, audit time-off policies, and trigger custom payroll deductions based on external business logic. Here is exactly how to do it using Truto's /tools endpoint and SDK, bypassing the need to write and maintain complex HRIS integration code from scratch.
Giving a Large Language Model (LLM) read and write access to a payroll and human resources platform is a high-stakes engineering challenge. You cannot afford hallucinations when dealing with compensation data, tax IDs, or employee status. If your team uses ChatGPT, check out our guide on connecting Justworks to ChatGPT, or if you are building on Anthropic's models, read our guide on connecting Justworks to Claude. For developers building custom autonomous workflows, you need a programmatic way to fetch these tools, constrain their inputs, and bind them to your agent framework.
This guide breaks down exactly how to fetch AI-ready tools for Justworks, bind them natively to an LLM using frameworks like LangChain, LangGraph, CrewAI, or the Vercel AI SDK, and execute complex HR workflows safely. For a deeper look at the architecture behind this approach, refer to our research on architecting AI agents and the SaaS integration bottleneck.
Why a Unified Tool Layer Matters for Agent Safety
Before writing a line of integration code, you must decide what layer your agent talks to. When dealing with an HRIS like Justworks, this choice determines the safety and reliability of your production system.
Direct API tools - mapping one tool per raw Justworks endpoint - seem fast to prototype but push severe vendor quirks directly into the LLM's context window. The model has to remember that Justworks formats currency differently than percentages, that member objects change shape depending on token scopes, and that certain endpoints require specific query filters just to operate. Every one of those quirks is a hallucination waiting to happen.
A unified proxy layer collapses these endpoints behind a strict, predictable schema. Your agent sees cleanly defined functions like list_all_justworks_members, create_a_justworks_deduction, and get_single_justworks_paystub_by_id. That gives you concrete safety wins:
- Smaller attack surface for hallucination. The LLM only chooses from defined, stable function names. It never invents endpoint paths or malformed payload structures.
- Deterministic input validation. Every tool has a strict JSON schema. Invalid arguments (like sending a string for a gross pay value instead of an integer) are rejected before they ever touch the Justworks API, allowing the agent to self-correct.
- Isolated authentication. The agent never sees bearer tokens, client secrets, or OAuth refresh tokens. The infrastructure handles the token lifecycle, completely removing credential leakage from the LLM threat model.
The Engineering Reality of the Justworks API
Giving an LLM access to external HR data sounds simple in a notebook environment. You write a Node.js function that makes a fetch request and wrap it in a tool decorator. Against a production system like Justworks, this approach collapses under the weight of vendor-specific API design.
If you hardcode these interactions into your agent, you will spend your engineering cycles writing defensive integration code instead of improving your model's reasoning capabilities. Here is what makes the Justworks API specifically challenging for AI agents.
The Silent Omission of Unscoped Fields
Justworks relies on highly granular OAuth scopes (member.pay:read, member.dob:read, member.employment:read). When you query a member object, the API does not throw an error if you request a field you lack scope for - it simply omits the field from the JSON response entirely. It also silently omits any fields that have empty values.
LLMs are terrible at handling silently missing keys. If an agent is tasked with calculating average compensation and the current_pay field is missing due to a scope issue, standard agents will often hallucinate a plausible salary based on the job title rather than recognizing the permission failure. Your tool descriptions must explicitly instruct the LLM on how to handle missing data points, and your proxy layer must enforce schema boundaries.
Strict Amount Formatting and Floating-Point Math
Payroll APIs are unforgiving regarding currency formatting. Justworks uses a strict integer-based system for amounts to avoid floating-point math errors. All fixed monetary amounts are expected in cents (e.g., sending 4500 for $45.00).
However, the API breaks this pattern for percentage deductions. If an amount type is percent, the value must carry exactly 4 decimal places (e.g., sending 37000 for 3.7%). Standard LLMs naturally default to decimals for currency and standard floats for percentages. If an agent sends 45 to a fixed deduction, it just deducted 45 cents instead of 45 dollars. The tool schema must strongly type and rigorously validate these formatting rules before the payload hits the network.
The Partial Success Bulk Update Trap
When creating or updating deductions via the /deductions or /deductions/bulk_update endpoints, Justworks accepts an array of items. When the request processes, Justworks returns a 201 Created or 200 OK status code - even if some or all of the individual entries in the array failed.
AI agents generally rely on HTTP status codes to determine tool success. If an agent sees a 201, it assumes the job is done and reports success to the user. To prevent this, your integration layer must intercept the 201 response, parse the returned items array, and explicitly check the success boolean on every single entry. If any entry shows success: false with an error_message, the tool must return a formatted error string back to the LLM so it can correct the payload and retry.
Handling Rate Limits in Production
When automating workflows across hundreds of employees, your agent will eventually hit Justworks API rate limits.
It is critical to understand that Truto does not retry, throttle, or apply backoff on rate limit errors. Truto's architecture is designed for transparency. When the upstream Justworks API returns an HTTP 429 Too Many Requests, Truto passes that error directly to the caller.
To make handling these limits standardized, Truto normalizes the upstream rate limit information into standard IETF HTTP headers regardless of how the vendor originally formatted them. You will receive:
ratelimit-limit: The total requests allowed in the current window.ratelimit-remaining: The number of requests left.ratelimit-reset: The time at which the window resets.
Your AI agent framework or calling code is entirely responsible for reading these headers, pausing execution, and retrying the tool call. Do not assume the infrastructure will magically absorb rate limits for you.
High-Leverage AI Agent Tools for Justworks
Instead of building individual endpoints, Truto maps the Justworks API into Proxy APIs and exposes them dynamically via the /tools endpoint. Below are the most effective hero tools for HR and payroll automation.
list_all_justworks_members
This tool retrieves the members (employees, contractors, owners) of the connected Justworks company. It supports cursor pagination and filtering by status or last updated date. Because fields are scope-gated, this is the foundational tool for auditing your workforce.
Contextual Usage Notes: Remind the agent that missing fields (like current_pay or emails) mean the value is either empty or the connection lacks the required read scope. Always filter by status=active when calculating current payroll metrics.
"Fetch all active employees updated in the last 30 days and extract their current department and manager IDs. If the manager ID is missing, flag the record for review."
get_single_justworks_paystub_by_id
Retrieves the detailed line items of a specific paystub, including earnings, employee deductions, and employer contributions.
Contextual Usage Notes: This tool requires a paystub_id, which must first be obtained by listing payrolls and then listing paystubs for that payroll. Ensure the agent knows all monetary amounts returned are integers representing cents.
"Retrieve the detailed paystub for ID paystub_12345. Calculate the total employer contributions in dollars and compare it against the gross pay."
create_a_justworks_deduction
Creates one or more payroll deductions in a single call. This is vital for syncing external benefit programs, equipment charges, or custom garnishments.
Contextual Usage Notes: The agent must format fixed amounts in cents and percent amounts with 4 decimal places. The tool response will contain an array of items; the agent must verify the success flag for every item submitted.
"Create a new fixed deduction of $50.00 for gym membership for member_67890 starting on 2024-05-01. Ensure the amount is formatted as cents before submitting."
justworks_deductions_bulk_update
Updates existing deductions. Useful for annual benefit enrollment changes or correcting errors across multiple employees at once.
Contextual Usage Notes: Requires the specific deduction_id. The agent should only send the fields that need changing. Like creation, it must parse the response array to confirm successful updates.
"Update the existing 401k deduction for deduction_abc123 to a new percent amount of 4.5%. Verify the update succeeded by checking the response payload."
list_all_justworks_time_off_requests
Lists time-off requests filtered by date range, status, or specific member. This tool powers capacity planning and PTO liability reporting.
Contextual Usage Notes: The start_date and end_date parameters are strictly required and must be formatted as YYYY-MM-DD. Pay close attention to the unit_type field (minutes, hours, or days) when aggregating total time off.
"List all approved time-off requests between 2024-06-01 and 2024-06-30. Sum the total amount of time off taken, converting all values into standard 8-hour days."
To view the complete inventory of Justworks tools, including company jurisdictions, custom fields, and payroll fees, visit the Justworks integration page.
Workflows in Action
Building single-tool prompts is easy. Orchestrating multi-step workflows that execute actual HR operations requires chaining tool outputs to subsequent tool inputs. Here are two concrete examples of how an agent uses these tools in the real world.
Scenario 1: End-of-Month Deduction Reconciliation
The Prompt:
"Find the active employee named Jane Doe. Once you have her member ID, create a new one-time fixed deduction of $125.00 for 'Equipment Fee'. Confirm if the deduction was successfully applied."
Agent Execution Sequence:
list_all_justworks_members: The agent calls this tool, filtering (or paging through) to locate the record wherenamematches "Jane Doe" andactiveis true. It extractsid(e.g.,member_98765).list_all_justworks_deduction_types: The agent queries available deduction codes to find the exact string required by Justworks for equipment fees (e.g.,equip_fee_01).create_a_justworks_deduction: The agent constructs the payload. It formats $125.00 as12500(cents), setsamount_typetofixed, and sets themember_id.- Verification: The tool returns a 201 response with an array. The agent reads the inner object, confirms
success: true, and replies to the user.
Output: The HR admin receives a confirmation message: "Successfully applied a $125.00 Equipment Fee deduction for Jane Doe (ID: member_98765). The system verified the entry was accepted."
Scenario 2: Time-Off Liability Auditing
The Prompt:
"Generate a time-off balance report for all employees as of December 31st. Once it's ready, calculate the total unused hours across the company."
Agent Execution Sequence:
create_a_justworks_time_off_balance_report: The agent initiates the async report, passingas_of_date: "2024-12-31". The tool returns areport_id.get_single_justworks_time_off_balance_report_by_id: The agent enters a polling loop, calling this tool with thereport_id. It checks thestatusfield. Ifpending, it waits.- Data Extraction: Once
statusisready, the agent extracts theitemsarray. - Calculation: The agent iterates through the items, looking at the
availableandunit_typefields. It converts any day or minute values into hours and sums the total.
Output: The Finance team receives a calculated response: "The time-off balance report generated successfully. As of Dec 31st, there is a total liability of 1,420 unused hours across all active policies."
Building Multi-Step Workflows
To build these multi-step workflows, your underlying infrastructure needs to fetch the JSON schemas from Truto, convert them into the format expected by your LLM framework, and execute a tool-calling loop.
Truto handles the schema generation dynamically based on the specific Justworks account connected. This means any custom fields configured by the specific customer are automatically injected into the tool schema.
Here is how you architect the agent execution loop using TypeScript. This example is conceptually framework-agnostic but mirrors patterns used in Vercel AI SDK and LangChain.
sequenceDiagram
participant YourApp as Your Agent Application
participant Truto as Truto API
participant LLM as LLM Provider (OpenAI/Anthropic)
participant Upstream as Justworks API
YourApp->>Truto: GET /integrated-account/<id>/tools
Truto-->>YourApp: Returns JSON schemas for Justworks methods
YourApp->>LLM: Pass prompt + bound Justworks tools
LLM-->>YourApp: Tool call requested (e.g., list_all_justworks_members)
YourApp->>Truto: Execute tool via Proxy API
Truto->>Upstream: Authenticated request to Justworks
Upstream-->>Truto: 200 OK (Member Data)
Truto-->>YourApp: Normalized JSON response
YourApp->>LLM: Append tool result to message history
LLM-->>YourApp: Final natural language answerFetching and Binding Tools
First, you retrieve the tools from Truto. You filter by methods (e.g., read, write) depending on the permissions you want to grant the agent.
// Fetching tools using standard Fetch API
async function getJustworksTools(accountId: string, trutoToken: string) {
const response = await fetch(
`https://api.truto.one/integrated-accounts/${accountId}/tools?methods[0]=read&methods[1]=write`,
{
headers: { Authorization: `Bearer ${trutoToken}` },
}
);
if (!response.ok) {
throw new Error(`Failed to fetch tools: ${response.statusText}`);
}
const tools = await response.json();
return tools;
}Once fetched, these schemas are passed to your LLM framework. In LangChain, this looks like llm.bindTools(formattedTools).
The Execution Loop and Error Handling
When the LLM decides to call a tool, it returns a tool call object containing the function name and the generated arguments. Your system executes the call against Truto's proxy endpoint.
Crucially, this is where you must handle the API quirks discussed earlier, such as Justworks's array-based responses and rate limit headers.
async function executeJustworksTool(toolCall: any, accountId: string, trutoToken: string) {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
const response = await fetch(
`https://api.truto.one/integrated-accounts/${accountId}/proxy/${toolCall.name}`,
{
method: "POST", // Truto proxies tool calls via POST
headers: {
Authorization: `Bearer ${trutoToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(toolCall.arguments)
}
);
// Handle Rate Limits passed directly from Justworks
if (response.status === 429) {
const resetTime = response.headers.get('ratelimit-reset');
const waitSeconds = resetTime ? Math.max(1, parseInt(resetTime) - Math.floor(Date.now() / 1000)) : Math.pow(2, attempt);
console.warn(`Rate limited. Waiting ${waitSeconds} seconds...`);
await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));
attempt++;
continue;
}
const data = await response.json();
// Trap the 201 Partial Success for Deductions
if (toolCall.name === "create_a_justworks_deduction" || toolCall.name === "justworks_deductions_bulk_update") {
const failures = data.items.filter((item: any) => item.success === false);
if (failures.length > 0) {
return JSON.stringify({
error: "Partial failure detected in bulk operation",
failed_items: failures
});
}
}
// Return successful data to the LLM context
return JSON.stringify(data);
}
throw new Error("Max retries exceeded for Justworks API");
}By returning the exact failure states back to the LLM as a JSON string (failed_items: failures), the agent can read the specific error_message from Justworks, recognize that its payload was invalid, and immediately attempt a correction without user intervention.
Moving from Scripts to Reliable Systems
Connecting an AI agent to an HRIS like Justworks is not just about making a REST call. It is about defending the LLM's context window from vendor-specific data models, managing complex floating-point formatting, and orchestrating secure, token-free infrastructure.
By leveraging an infrastructure layer that converts APIs into strict JSON schemas, your engineering team can focus on agent reasoning and multi-step workflow logic rather than debugging OAuth refresh loops and undocumented API behaviors.
FAQ
- Can AI agents safely write data to Justworks?
- Yes, but write operations should be tightly scoped using specialized proxy tools with strict JSON schemas. Bulk operations like deduction updates require careful error handling, as Justworks returns a 201 status even if individual line items fail.
- How do AI agents handle Justworks API rate limits?
- Truto passes 429 Too Many Requests errors directly to the caller, normalizing the rate limit information into standard headers (ratelimit-limit, ratelimit-remaining, ratelimit-reset). The AI agent framework is responsible for reading these headers and executing exponential backoff.
- Does Truto support LangChain and other agent frameworks?
- Yes, Truto exposes Justworks operations as unified JSON schemas via the /tools endpoint, which can be natively bound to LangChain, CrewAI, Vercel AI SDK, or any other LLM framework using standard tool calling.
- How are Justworks payroll amounts formatted for AI tools?
- Justworks strictly separates fixed amounts and percentages. Fixed monetary amounts are formatted as integers in cents (e.g., 4500 for $45.00), while percentages require four decimal places (e.g., 37000 for 3.7%).