AI Agent Integration Guide
This guide explains how AI agents and LLM-powered applications can integrate with the InCRM API for automated CRM operations, data retrieval, and workflow management.
Quick Start for AI Agents
- Get an API key — create one via the UI or API (see Authentication)
- Fetch the OpenAPI spec —
GET /api/v1/openapi.json(no auth required) - Start making requests — use the API key in the
X-Api-Keyheader
OpenAPI Specification
The full API schema is available at:
GET /api/v1/openapi.json
This endpoint requires no authentication and returns an OpenAPI 3.0 document. Use it to:
- Discover all available endpoints and their parameters
- Generate client code or function definitions
- Build tool descriptions for LLM function calling
Fetching the Spec
curl https://api.incrm.app/api/v1/openapi.json -o openapi.json
const response = await fetch('https://api.incrm.app/api/v1/openapi.json')
const spec = await response.json()
Using the TypeScript SDK
The @incrm-app/sdk package provides fully typed functions for every API endpoint, generated from the OpenAPI spec.
Setup
import { InCRMClient, listClients, getClient, createInvoice } from '@incrm-app/sdk'
const incrm = new InCRMClient({
baseUrl: 'https://api.incrm.app',
apiKey: process.env.INCRM_API_KEY!,
})
Common Operations
List all clients:
const { data: response } = await listClients({ query: { page: 1, perPage: 100 } })
const clients = response.data
const totalClients = response.meta.total
Get a specific client:
const { data: response } = await getClient({ path: { id: 'client-id' } })
const client = response.data
Create an invoice:
const { data: response } = await createInvoice({
body: {
clientId: 'client-id',
items: [
{ description: 'Consulting', quantity: 2, unitPrice: 150 },
],
},
})
LLM Function Calling
OpenAI Function Calling
Convert OpenAPI endpoints to OpenAI function definitions:
import { readFileSync } from 'fs'
const spec = JSON.parse(readFileSync('openapi.json', 'utf-8'))
function endpointToFunction(path: string, method: string, operation: any) {
return {
type: 'function',
function: {
name: operation.operationId,
description: operation.summary || `${method.toUpperCase()} ${path}`,
parameters: extractParameters(operation),
},
}
}
LangChain Tools
Build LangChain-compatible tools from the API:
import { DynamicStructuredTool } from '@langchain/core/tools'
import { z } from 'zod'
import { InCRMClient, listClients, createClient } from '@incrm-app/sdk'
const incrm = new InCRMClient({
baseUrl: 'https://api.incrm.app',
apiKey: process.env.INCRM_API_KEY!,
})
const listClientsTool = new DynamicStructuredTool({
name: 'list_clients',
description: 'List CRM clients with optional filtering and pagination',
schema: z.object({
page: z.number().optional().describe('Page number (default: 1)'),
perPage: z.number().optional().describe('Items per page (default: 10, max: 100)'),
where: z.string().optional().describe('Filter expression, e.g. "firstName:John"'),
}),
func: async ({ page, perPage, where }) => {
const { data } = await listClients({ query: { page, perPage, where } })
return JSON.stringify(data)
},
})
const createClientTool = new DynamicStructuredTool({
name: 'create_client',
description: 'Create a new CRM client record',
schema: z.object({
firstName: z.string().describe('Client first name'),
lastName: z.string().describe('Client last name'),
email: z.string().optional().describe('Client email address'),
phone: z.string().optional().describe('Client phone number'),
}),
func: async (params) => {
const { data } = await createClient({ body: params })
return JSON.stringify(data)
},
})
Pagination Traversal Pattern
When an AI agent needs to process all records of a certain type, use this traversal pattern:
async function fetchAllRecords<T>(
fetcher: (query: { page: number; perPage: number }) => Promise<{
data: { data: T[]; meta: { next: number | null } }
}>,
): Promise<T[]> {
const allRecords: T[] = []
let page = 1
while (true) {
const { data: response } = await fetcher({ page, perPage: 100 })
allRecords.push(...response.data)
if (!response.meta.next) break
page = response.meta.next
// Respect rate limits
await new Promise(resolve => setTimeout(resolve, 200))
}
return allRecords
}
// Usage
const allClients = await fetchAllRecords((query) => listClients({ query }))
const allInvoices = await fetchAllRecords((query) => listInvoices({ query }))
Rate Limit Awareness
AI agents must respect rate limits to avoid service disruption. The global limit is 50 requests per 60 seconds.
Strategies
- Monitor headers — check
X-RateLimit-Remainingafter each request. - Add delays — insert 200ms+ delays between sequential requests.
- Batch with pagination — use
perPage=100to minimize request count. - Handle 429 gracefully — implement exponential backoff on rate limit errors.
async function safeRequest<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn()
} catch (error: any) {
if (error.status === 429) {
const resetHeader = error.headers?.['x-ratelimit-reset']
const waitMs = resetHeader
? Math.max(0, Number(resetHeader) * 1000 - Date.now())
: 5000
await new Promise(resolve => setTimeout(resolve, waitMs))
return fn()
}
throw error
}
}
Permission Model
API keys can be scoped to specific permissions, limiting what an AI agent can do.
Recommended Scopes for AI Agents
Read-only agent (data analysis, reporting):
{
"name": "AI Read Agent",
"scopes": [
"clients.read",
"invoices.read",
"quotes.read",
"products.read",
"services.read",
"employees.read"
]
}
CRM automation agent (create and update records):
{
"name": "AI CRM Agent",
"scopes": [
"clients.read", "clients.create", "clients.update",
"invoices.read", "invoices.create",
"quotes.read", "quotes.create", "quotes.update"
]
}
Full access (omit scopes or leave empty):
{
"name": "AI Full Access",
"scopes": []
}
An empty scopes array inherits all permissions from the key owner.
Handling Permission Errors
When an agent attempts an action outside its scope, the API returns:
{
"success": false,
"error": {
"code": 401014,
"message": "Insufficient permissions for this action"
}
}
The agent should report this to the user rather than retrying.
Multi-Tenant Context
Each API key is bound to a specific business. All data returned by the API is scoped to that business — the agent cannot access data from other tenants.
This means:
GET /api/v1/clientreturns only clients belonging to the key's businessPOST /api/v1/invoicecreates an invoice within the key's business- Cross-business operations are not possible through a single API key
Webhook-Driven Agents
Instead of polling the API, AI agents can react to events in real-time using webhooks:
- Register a webhook for events your agent cares about (e.g.,
client.created,invoice.updated) - Receive events at your webhook URL as they happen
- Process and act — use the event data to trigger agent workflows
This is more efficient than polling and ensures your agent responds to changes within seconds.
See Webhooks for setup instructions and signature verification.
Example: Complete AI Agent Setup
import { InCRMClient, listClients, createInvoice } from '@incrm-app/sdk'
// 1. Initialize the client
const incrm = new InCRMClient({
baseUrl: 'https://api.incrm.app',
apiKey: process.env.INCRM_API_KEY!,
})
// 2. Fetch all clients
const { data: clientsResponse } = await listClients({
query: { page: 1, perPage: 100, orderBy: 'createdAt:desc' },
})
console.log(`Found ${clientsResponse.meta.total} clients`)
// 3. Process each client
for (const client of clientsResponse.data) {
console.log(`${client.firstName} ${client.lastName} (${client.email})`)
}
// 4. Create an invoice for the first client
if (clientsResponse.data.length > 0) {
const { data: invoiceResponse } = await createInvoice({
body: {
clientId: clientsResponse.data[0].id,
items: [
{ description: 'Monthly retainer', quantity: 1, unitPrice: 2000 },
],
},
})
console.log(`Created invoice: ${invoiceResponse.data.id}`)
}