API Reference
Base URL: https://agent.curvestone.ai
Authentication
All requests require a Bearer token in the Authorization header. API keys are prefixed with cs_live_ for production and cs_test_ for sandbox environments.
| Header | Value | Required |
|---|---|---|
Authorization | Bearer cs_live_xxxxxxxxxxxx | Yes |
Curvestone-Version | 2026-02-21 | Yes |
Content-Type | application/json | Yes |
Core Verbs
The three primary actions. Each is an alias for POST /jobs with the corresponding type.
/checkAlias for POST /jobs with type: "check". Submits documents for compliance checking against the selected case type, modifiers, and depth.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
case_type | string | Yes | Case type for the job (e.g. "residential_mortgage", "buy_to_let"). |
depth | string | Yes | Check depth tier: "admin_check", "soft_check", "full_check", or "mortgage_club_check". |
variations | string[] | No | Modifier IDs that adjust the skill set (e.g. ["debt_consolidation"]). |
documents | File[] | Yes | One or more documents to analyse. Supported: PDF, DOCX, XLSX, PNG, JPG. |
reference | string | No | Your internal case reference. |
idempotency_key | string | No | Unique key for safe retries. |
Request
from curvestone import Agentagent = Agent(api_key="cs_live_xxxxxxxxxxxx")result = agent.check(case_type="residential_mortgage",depth="full_check",variations=["debt_consolidation"],documents=[open("fact_find.pdf", "rb"),open("suitability_letter.pdf", "rb"),open("payslips.pdf", "rb"),],reference="CASE-2026-00451",)print(f"Triage: {result.triage}") # green | amber | redprint(f"Skills: {len(result.skills)}")print(f"Time: {result.processing_time}")
Response
{"id": "job_7kTx9mNpQ2","type": "check","status": "queued","reference": "CASE-2026-00451","created_at": "2026-02-21T14:30:00Z"}
/askConversational endpoint. Ask a natural-language question about a completed job, a document, or the compliance framework.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
question | string | Yes | The question to ask. |
job_id | string | No | Reference a previous job for context-aware answers. |
documents | File[] | No | Optional documents to ask about directly. |
Request
from curvestone import Agentagent = Agent(api_key="cs_live_xxxxxxxxxxxx")result = agent.ask("Why was the suitability letter rated amber?",job_id="job_abc123",)print(result.answer)print(f"Sources: {len(result.sources)}")
Response
{"id": "job_qR4tY8wZ1","type": "ask","status": "completed","answer": "The suitability letter received an amber rating because the affordability buffer was within 2% of the threshold, which constitutes a borderline pass. Additionally, the debt consolidation rationale was generic and lacked client-specific justification.","sources": [{ "document": "suitability_letter.pdf", "page": 3, "excerpt": "Client affordability assessed at..." },{ "skill": "Affordability Assessment", "question_id": "q_12" }],"created_at": "2026-02-21T15:00:00Z"}
/monitorCreate a recurring monitor that watches for changes and optionally triggers downstream actions.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Monitor type: "website_change", "criteria_update", or "regulation_change". |
target | string | Yes | URL or resource identifier to monitor. |
schedule | string | Yes | Frequency: "hourly", "daily", "every_15_days", "weekly", "monthly". |
on_finding | object | No | Action to take when a change is detected (e.g. { "then": "check" }). |
webhook_url | string | No | URL for monitor event callbacks. |
Request
from curvestone import Agentagent = Agent(api_key="cs_live_xxxxxxxxxxxx")monitor = agent.monitor(type="website_change",target="https://lender.example.com/criteria",schedule="every_15_days",on_finding={"then": "check"},)print(f"Monitor ID: {monitor.id}")print(f"Next run: {monitor.next_run_at}")
Response
{"id": "mon_xK2pL9rT","type": "website_change","status": "active","target": "https://lender.example.com/criteria","schedule": "every_15_days","next_run_at": "2026-03-08T00:00:00Z","created_at": "2026-02-21T14:30:00Z"}
Jobs
Create, retrieve, and list jobs.
/jobs/:idRetrieve a single job by ID. Use this to poll job status or fetch completed results.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The job ID (path parameter). |
Response
{"id": "job_7kTx9mNpQ2","type": "check","status": "completed","triage": "amber","reference": "CASE-2026-00451","processing_time": "147s","skills_executed": 7,"questions_answered": 42,"created_at": "2026-02-21T14:30:00Z","completed_at": "2026-02-21T14:32:27Z"}
/jobsList jobs with filtering and pagination. Returns most recent first.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
status | string | No | Filter by status: "queued", "in_progress", "completed", "failed". |
type | string | No | Filter by job type: "check", "ask", or "monitor". |
reference | string | No | Filter by your internal reference. |
limit | number | No | Number of results (default 20, max 100). |
starting_after | string | No | Cursor for pagination (job ID). |
Response
{"data": [{ "id": "job_7kTx9mNpQ2", "type": "check", "status": "completed", "triage": "amber", "created_at": "2026-02-21T14:30:00Z" },{ "id": "job_qR4tY8wZ1", "type": "ask", "status": "completed", "created_at": "2026-02-21T15:00:00Z" }],"has_more": true,"next_cursor": "job_qR4tY8wZ1"}
/jobsUniversal job creation endpoint. Accepts any job type (check, ask, monitor) via the type field.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Job type: "check", "ask", or "monitor". |
case_type | string | No | Case type for the job (e.g. "residential_mortgage"). Required for check jobs. |
depth | string | No | Check depth: "admin_check", "soft_check", "full_check", or "mortgage_club_check". |
variations | string[] | No | Modifier IDs to apply (e.g. ["debt_consolidation", "interest_only"]). |
documents | File[] | No | Uploaded documents for analysis. Accepts PDF, DOCX, XLSX, images. |
reference | string | No | Your internal case reference for tracking. |
webhook_url | string | No | URL to receive job completion callback. |
idempotency_key | string | No | Unique key to prevent duplicate job creation. |
Response
{"id": "job_7kTx9mNpQ2","type": "check","status": "queued","reference": "CASE-2026-00451","created_at": "2026-02-21T14:30:00Z"}
Configuration
Discover available dimensions for your organisation.
/config/dimensionsRetrieve the full configuration matrix: case types, modifiers, depths, and scoring modes available to your organisation.
Response
{"case_types": [{ "id": "residential_mortgage", "name": "Residential Mortgage", "skills": 5 },{ "id": "buy_to_let", "name": "Buy-to-Let", "skills": 6 },{ "id": "commercial_mortgage", "name": "Commercial Mortgage", "skills": 5 }],"modifiers": [{ "id": "debt_consolidation", "name": "Debt Consolidation", "applies_to": ["residential_mortgage"] },{ "id": "interest_only", "name": "Interest Only", "applies_to": ["residential_mortgage", "buy_to_let"] }],"depths": [{ "id": "admin_check", "name": "Admin Check" },{ "id": "soft_check", "name": "Soft Check" },{ "id": "full_check", "name": "Full Check" },{ "id": "mortgage_club_check", "name": "Mortgage Club" }],"scoring": ["pass_fail", "rag"]}
Webhooks
Register endpoints to receive real-time event notifications.
/webhooksRegister a webhook endpoint to receive real-time event notifications.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The HTTPS URL to receive webhook events. |
events | string[] | Yes | Events to subscribe to: "job.completed", "job.failed", "review.required", "monitor.triggered". |
secret | string | No | Signing secret for HMAC verification. Auto-generated if omitted. |
Response
{"id": "wh_T3nP8kL2","url": "https://yourapp.example.com/webhooks/curvestone","events": ["job.completed", "job.failed", "review.required"],"secret": "whsec_a1b2c3d4e5f6g7h8","status": "active","created_at": "2026-02-21T14:30:00Z"}
Billing
Usage, spend, and partner-level analytics.
/billingRetrieve current billing period usage, spend, and plan details for your organisation.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
period | string | No | Billing period in YYYY-MM format (defaults to current month). |
Response
{"period": "2026-02","plan": "self_serve","usage": {"checks": { "count": 142, "spend": "£426.00" },"asks": { "count": 87, "spend": "£4.35" },"monitors": { "count": 2, "spend": "£30.00" }},"total_spend": "£460.35","free_checks_remaining": 0}
/partner/usageNetwork and partner usage analytics. Returns aggregated usage across all child brokerages.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
period | string | No | Billing period in YYYY-MM format. |
group_by | string | No | Group results by: "brokerage", "broker", "case_type", or "day". |
Response
{"period": "2026-02","total_jobs": 1847,"total_spend": "£5,541.00","brokerages": [{ "id": "org_abc", "name": "Premier Mortgages", "jobs": 423, "spend": "£1,269.00" },{ "id": "org_def", "name": "Capital Finance", "jobs": 312, "spend": "£936.00" }],"has_more": true,"next_cursor": "org_ghi"}
Errors
All errors return a consistent JSON body with a type, message, and optional param field.
| Status | Error | Description |
|---|---|---|
400 | bad_request | Invalid parameters or missing required fields. |
401 | unauthorized | Missing or invalid API key. |
403 | forbidden | Insufficient permissions for this action or resource. |
404 | not_found | Resource does not exist or is not accessible. |
429 | rate_limited | Too many requests. Back off and retry with exponential backoff. |
500 | internal_error | Server error. Retry the request or contact support. |
Error response format
{"error": {"type": "bad_request","message": "case_type is required"}}
Rate Limits
Rate limits are enforced per API key. Exceeding the limit returns a 429 status with a Retry-After header.
| Plan | Rate Limit |
|---|---|
| Self-Serve | 60 requests / minute |
| Enterprise | 300 requests / minute |