API Documentation
Integrate OperantOS products into your systems using webhook endpoints. All endpoints accept JSON payloads and return JSON responses.
Base URL: https://operantos.ai/api
Authentication
All API requests require authentication via a product-specific API key sent in a custom HTTP header. API keys are generated in your dashboard under Settings > API Keys.
| Product | Header Name | Format |
|---|---|---|
| CloseBot | X-CloseBot-Key | cb_live_... |
| LedgerPilot | X-LedgerPilot-Key | lp_live_... |
| FixFlow | X-FixFlow-Key | ff_live_... |
Include Content-Type: application/json on all requests with a body.
Security: Keep your API keys secret. Do not expose them in client-side code, public repositories, or browser requests. Rotate keys immediately if compromised.
CloseBot Webhook
/api/closebot/webhookCreate an estimate and trigger the automated follow-up sequence. CloseBot begins the configured follow-up schedule immediately upon receiving a valid request.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
customer_name | string | Yes | Full name of the customer |
customer_email | string | Yes* | Customer email for email follow-ups |
customer_phone | string | Yes* | Customer phone for SMS follow-ups (E.164 format) |
estimate_amount | number | No | Estimate total in dollars (e.g., 8500.00) |
service_type | string | No | Type of service (e.g., "furnace replacement", "roof repair") |
description | string | No | Additional context or notes about the estimate |
external_id | string | No | Your system's unique ID for deduplication |
* At least one of customer_email or customer_phone is required.
Response
{
"id": "est_a1b2c3d4e5f6",
"status": "scheduled",
"followups_scheduled": 4,
"first_followup_at": "2026-04-08T18:00:00Z"
}Examples
curl
curl -X POST https://operantos.ai/api/closebot/webhook \
-H "Content-Type: application/json" \
-H "X-CloseBot-Key: cb_live_your_api_key" \
-d '{
"customer_name": "Sarah Johnson",
"customer_email": "[email protected]",
"customer_phone": "+14165551234",
"estimate_amount": 8500.00,
"service_type": "furnace replacement",
"description": "High-efficiency furnace install for 2-storey home",
"external_id": "est-001"
}'JavaScript (fetch)
const response = await fetch("https://operantos.ai/api/closebot/webhook", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CloseBot-Key": "cb_live_your_api_key",
},
body: JSON.stringify({
customer_name: "Sarah Johnson",
customer_email: "[email protected]",
customer_phone: "+14165551234",
estimate_amount: 8500.00,
service_type: "furnace replacement",
description: "High-efficiency furnace install for 2-storey home",
external_id: "est-001",
}),
});
const data = await response.json();
console.log(data.id, data.followups_scheduled);Python (requests)
import requests
response = requests.post(
"https://operantos.ai/api/closebot/webhook",
headers={
"Content-Type": "application/json",
"X-CloseBot-Key": "cb_live_your_api_key",
},
json={
"customer_name": "Sarah Johnson",
"customer_email": "[email protected]",
"customer_phone": "+14165551234",
"estimate_amount": 8500.00,
"service_type": "furnace replacement",
"description": "High-efficiency furnace install for 2-storey home",
"external_id": "est-001",
},
)
data = response.json()
print(data["id"], data["followups_scheduled"])FixFlow Webhook
/api/fixflow/requestsCreate a maintenance request programmatically. FixFlow triages the issue, dispatches the appropriate vendor, and sends tenant acknowledgment automatically.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
unit_id | string | Yes | Property unit identifier (e.g., "4B" or "unit-123") |
tenant_name | string | Yes | Tenant full name |
tenant_phone | string | Yes | Tenant phone number (E.164 format) |
description | string | Yes | Description of the maintenance issue |
category | string | No | Issue category: plumbing, hvac, electrical, appliance, structural, pest, general |
urgency | string | No | Urgency level: emergency, urgent, routine (auto-classified if omitted) |
Response
{
"id": "req_x7y8z9w0v1u2",
"status": "triaged",
"category": "plumbing",
"urgency": "urgent",
"vendor_dispatched": true,
"tenant_notified": true,
"estimated_response_minutes": 45
}Examples
curl
curl -X POST https://operantos.ai/api/fixflow/requests \
-H "Content-Type: application/json" \
-H "X-FixFlow-Key: ff_live_your_api_key" \
-d '{
"unit_id": "4B",
"tenant_name": "Mike Chen",
"tenant_phone": "+14165559876",
"description": "Kitchen faucet is leaking badly, water pooling on floor",
"category": "plumbing",
"urgency": "urgent"
}'JavaScript (fetch)
const response = await fetch("https://operantos.ai/api/fixflow/requests", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-FixFlow-Key": "ff_live_your_api_key",
},
body: JSON.stringify({
unit_id: "4B",
tenant_name: "Mike Chen",
tenant_phone: "+14165559876",
description: "Kitchen faucet is leaking badly, water pooling on floor",
category: "plumbing",
urgency: "urgent",
}),
});
const data = await response.json();
console.log(data.id, data.urgency, data.estimated_response_minutes);Python (requests)
import requests
response = requests.post(
"https://operantos.ai/api/fixflow/requests",
headers={
"Content-Type": "application/json",
"X-FixFlow-Key": "ff_live_your_api_key",
},
json={
"unit_id": "4B",
"tenant_name": "Mike Chen",
"tenant_phone": "+14165559876",
"description": "Kitchen faucet is leaking badly, water pooling on floor",
"category": "plumbing",
"urgency": "urgent",
},
)
data = response.json()
print(data["id"], data["urgency"], data["estimated_response_minutes"])Rate Limits
All webhook endpoints are rate-limited to protect service quality. Limits apply per API key.
| Endpoint | Limit | Window |
|---|---|---|
| /api/closebot/webhook | 100 requests | Per minute |
| /api/fixflow/requests | 60 requests | Per minute |
| All endpoints (daily) | 10,000 requests | Per day |
Rate-limited responses include headers to help you manage your usage:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed in the current window |
| X-RateLimit-Remaining | Requests remaining in the current window |
| X-RateLimit-Reset | Unix timestamp when the rate limit resets |
| Retry-After | Seconds to wait before retrying (only on 429 responses) |
When rate-limited, wait the number of seconds indicated by the Retry-After header before retrying. Use exponential backoff for reliability.
Error Codes
All errors return a JSON body with error and message fields.
{
"error": "validation_error",
"message": "customer_name is required"
}| Status | Error Code | Description | Action |
|---|---|---|---|
| 400 | validation_error | Request body is missing required fields or contains invalid data | Check field names, types, and required fields |
| 401 | unauthorized | API key is missing or invalid | Verify your API key in Settings > API Keys |
| 403 | forbidden | API key does not have permission for this endpoint | Ensure you are using the correct product-specific key |
| 429 | rate_limited | Too many requests in the current time window | Wait for Retry-After seconds, then retry with exponential backoff |
| 500 | internal_error | An unexpected error occurred on our servers | Retry after a short delay. Contact support if persistent |