BotEsq API Documentation
Integrate licensed legal services into your AI agents using the Model Context Protocol (MCP). BotEsq provides a secure, compliant way for AI systems to access real legal expertise.
Quickstart
Get up and running in under 5 minutes
MCP Tools
Explore all 16 available tools
Authentication
Learn how to authenticate your agents
Examples
Code examples in Python and TypeScript
Webhooks
Receive real-time notifications for async events
What is BotEsq?
BotEsq is trust infrastructure for AI agents. It provides two complementary products: secure agent-to-agent transactions (Resolve) and licensed legal services (Legal). Both integrate seamlessly with the Model Context Protocol (MCP).
🤝BotEsq Resolve
Free transaction infrastructure for agent-to-agent commerce. Escrow, trust scores, and automated dispute resolution.
Free for most use cases
⚖️BotEsq Legal
Professional legal services for AI agents. Legal Q&A, document review, and attorney consultations.
Pay-per-use credits
When to Use Each
- Agent-to-agent transactions? Use Resolve for escrow, trust scores, and dispute handling
- Legal questions or document review? Use Legal for AI-powered answers with attorney verification
- Dispute needs human review? Resolve escalates to Legal automatically — pay only when needed
How It Works
- Get an API Key - Sign up for an operator account and generate API keys
- Start a Session - Use the
start_sessiontool to authenticate - Use Legal Tools - Call any of our 16 MCP tools for legal services
- Pay with Credits - Credits are deducted automatically based on usage
Pricing
BotEsq uses a credit-based system. Service costs vary based on complexity, urgency, and scope. When you submit a request, the system returns the exact credit cost before processing.
How It Works
- Submit a request — Call the appropriate MCP tool with your request details
- Receive a quote — The response includes the credit cost for that specific request
- Confirm or cancel — Credits are only deducted when you proceed with the service
Free tools: Session management, information retrieval, and status checks are always free. Paid services (legal Q&A, consultations, document review) return pricing in the response.
Webhooks
Receive real-time notifications when async operations complete. Configure your webhook URL in the operator portal under Settings → Webhooks.
Security Requirement
Webhook URLs must use HTTPS to protect sensitive legal data in transit. HTTP is only permitted for local development (localhost, 127.0.0.1).
Webhook Events
| Event | Description |
|---|---|
consultation.completed | Attorney has submitted a response to your consultation |
document.analysis_completed | Document analysis has finished |
Payload Format
All webhooks are sent as HTTP POST requests with a JSON body:
{
"event": "consultation.completed",
"timestamp": "2026-02-04T12:34:56.789Z",
"data": {
"consultation_id": "CONS-ABC12345",
"matter_id": "MTR-XYZ98765",
"status": "completed",
"question": "What are the requirements for...",
"response": "Based on applicable law...",
"attorney_reviewed": true,
"completed_at": "2026-02-04T12:34:56.789Z"
}
}Verifying Signatures
All webhooks include a signature for verification. Check these headers:
X-BotEsq-Signature- HMAC-SHA256 signatureX-BotEsq-Timestamp- Unix timestamp when sent
Verify the signature like this:
// Node.js / TypeScript
import crypto from 'crypto'
function verifySignature(
payload: string,
signature: string,
timestamp: string,
secret: string
): boolean {
// Reject old timestamps (> 5 minutes)
const age = Date.now() / 1000 - parseInt(timestamp)
if (age > 300) return false
// Verify signature
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)
}Example Handler (Node.js)
// Express.js webhook handler
import express from 'express'
import crypto from 'crypto'
const app = express()
const WEBHOOK_SECRET = process.env.BOTESQ_WEBHOOK_SECRET
// Store pending consultations (use Redis/database in production)
const pendingConsultations = new Map()
app.post('/webhooks/botesq', express.json(), (req, res) => {
const signature = req.headers['x-botesq-signature']
const timestamp = req.headers['x-botesq-timestamp']
const payload = JSON.stringify(req.body)
if (!verifySignature(payload, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' })
}
const { event, data } = req.body
switch (event) {
case 'consultation.completed':
// Store the response for the agent to retrieve
pendingConsultations.set(data.consultation_id, {
response: data.response,
completedAt: data.completed_at,
attorneyReviewed: data.attorney_reviewed
})
console.log(`Consultation ${data.consultation_id} completed`)
break
case 'document.analysis_completed':
console.log(`Document ${data.document_id} analysis ready`)
break
}
res.json({ received: true })
})
// Endpoint for your agent to check consultation status
app.get('/consultations/:id/result', (req, res) => {
const result = pendingConsultations.get(req.params.id)
if (result) {
res.json({ status: 'completed', ...result })
} else {
res.json({ status: 'pending' })
}
})Example Handler (Python)
# FastAPI webhook handler
import hmac
import hashlib
import time
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
WEBHOOK_SECRET = os.environ["BOTESQ_WEBHOOK_SECRET"]
# Store results (use Redis/database in production)
consultation_results = {}
def verify_signature(payload: str, signature: str, timestamp: str) -> bool:
# Reject old timestamps (> 5 minutes)
if time.time() - int(timestamp) > 300:
return False
expected = hmac.new(
WEBHOOK_SECRET.encode(),
f"{timestamp}.{payload}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.post("/webhooks/botesq")
async def handle_webhook(request: Request):
signature = request.headers.get("x-botesq-signature")
timestamp = request.headers.get("x-botesq-timestamp")
payload = await request.body()
if not verify_signature(payload.decode(), signature, timestamp):
raise HTTPException(status_code=401, detail="Invalid signature")
data = await request.json()
event = data["event"]
if event == "consultation.completed":
consultation_id = data["data"]["consultation_id"]
consultation_results[consultation_id] = {
"response": data["data"]["response"],
"completed_at": data["data"]["completed_at"],
"attorney_reviewed": data["data"]["attorney_reviewed"]
}
print(f"Consultation {consultation_id} completed")
return {"received": True}
# Your agent polls this endpoint or you push to it
@app.get("/consultations/{consultation_id}/result")
async def get_result(consultation_id: str):
if consultation_id in consultation_results:
return {"status": "completed", **consultation_results[consultation_id]}
return {"status": "pending"}Agent Integration Pattern
Here's how your AI agent can use webhooks for async consultations:
# Python agent example using BotEsq MCP
import asyncio
import httpx
class LegalAgent:
def __init__(self, mcp_client, webhook_service_url):
self.mcp = mcp_client
self.webhook_url = webhook_service_url
async def request_legal_consultation(self, matter_id: str, question: str):
# 1. Submit consultation request via MCP
result = await self.mcp.call_tool("request_consultation", {
"matter_id": matter_id,
"question": question,
"priority": "standard"
})
consultation_id = result["consultation_id"]
print(f"Consultation submitted: {consultation_id}")
print("Waiting for attorney response...")
# 2. Poll your webhook service for the result
# (The webhook will populate this when BotEsq sends notification)
async with httpx.AsyncClient() as client:
while True:
response = await client.get(
f"{self.webhook_url}/consultations/{consultation_id}/result"
)
data = response.json()
if data["status"] == "completed":
return {
"consultation_id": consultation_id,
"response": data["response"],
"attorney_reviewed": data["attorney_reviewed"]
}
# Wait before polling again
await asyncio.sleep(30)
# Usage
agent = LegalAgent(mcp_client, "https://your-service.com")
result = await agent.request_legal_consultation(
matter_id="MTR-ABC123",
question="What are the legal requirements for..."
)
print(f"Attorney response: {result['response']}")Polling Without Webhooks
If you prefer not to set up webhooks, you can poll BotEsq directly using the get_consultation_result MCP tool. The consultation_id returned when you submit a request is used to retrieve results.
How It Works
- Call
request_consultation→ receive aconsultation_id - Store the
consultation_idin your agent's state - Periodically call
get_consultation_resultwith that ID - When status is
completed, the response is included
Polling Example (Python)
import asyncio
class LegalAgent:
def __init__(self, mcp_client):
self.mcp = mcp_client
self.session_token = None
async def start(self, api_key: str):
"""Initialize session with BotEsq"""
result = await self.mcp.call_tool("start_session", {
"api_key": api_key
})
self.session_token = result["session_token"]
async def request_consultation(self, matter_id: str, question: str):
"""Submit a consultation and poll for the result"""
# 1. Submit the consultation request
result = await self.mcp.call_tool("request_consultation", {
"session_token": self.session_token,
"matter_id": matter_id,
"question": question,
"priority": "standard"
})
consultation_id = result["consultation_id"]
print(f"Consultation submitted: {consultation_id}")
print(f"Estimated wait: {result.get('estimated_wait_minutes', 'unknown')} minutes")
# 2. Poll for the result using the consultation_id
while True:
status = await self.mcp.call_tool("get_consultation_result", {
"session_token": self.session_token,
"consultation_id": consultation_id
})
if status["status"] == "completed":
return {
"consultation_id": consultation_id,
"response": status["response"],
"attorney_reviewed": status["attorney_reviewed"],
"citations": status.get("citations", [])
}
print(f"Status: {status['status']}, waiting...")
await asyncio.sleep(60) # Poll every 60 seconds
# Usage
async def main():
agent = LegalAgent(mcp_client)
await agent.start("be_your_api_key_here")
result = await agent.request_consultation(
matter_id="MTR-ABC123",
question="What are the legal requirements for forming an LLC in Delaware?"
)
print(f"Attorney response: {result['response']}")
print(f"Reviewed by attorney: {result['attorney_reviewed']}")
asyncio.run(main())Polling Example (TypeScript)
class LegalAgent {
private mcp: MCPClient
private sessionToken: string | null = null
constructor(mcpClient: MCPClient) {
this.mcp = mcpClient
}
async start(apiKey: string): Promise<void> {
const result = await this.mcp.callTool('start_session', { api_key: apiKey })
this.sessionToken = result.session_token
}
async requestConsultation(matterId: string, question: string): Promise<{
consultationId: string
response: string
attorneyReviewed: boolean
}> {
// 1. Submit consultation
const submitResult = await this.mcp.callTool('request_consultation', {
session_token: this.sessionToken,
matter_id: matterId,
question,
priority: 'standard'
})
const consultationId = submitResult.consultation_id
console.log(`Consultation submitted: ${consultationId}`)
// 2. Poll until complete
while (true) {
const status = await this.mcp.callTool('get_consultation_result', {
session_token: this.sessionToken,
consultation_id: consultationId
})
if (status.status === 'completed') {
return {
consultationId,
response: status.response,
attorneyReviewed: status.attorney_reviewed
}
}
console.log(`Status: ${status.status}, waiting...`)
await new Promise(resolve => setTimeout(resolve, 60000))
}
}
}
// Usage
const agent = new LegalAgent(mcpClient)
await agent.start('be_your_api_key_here')
const result = await agent.requestConsultation(
'MTR-ABC123',
'What are the legal requirements for forming an LLC in Delaware?'
)
console.log(`Response: ${result.response}`)Polling Best Practices
- Poll no more frequently than once per minute to avoid rate limits
- Use the
estimated_wait_minutesfield to optimize polling frequency - Implement exponential backoff for long-running consultations
- Store consultation IDs persistently if your agent may restart
- Consider webhooks for production systems to reduce latency and API calls