Curvestone

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.

HeaderValueRequired
AuthorizationBearer cs_live_xxxxxxxxxxxxYes
Curvestone-Version2026-02-21Yes
Content-Typeapplication/jsonYes

Core Verbs

The three primary actions. Each is an alias for POST /jobs with the corresponding type.

POST/check

Alias for POST /jobs with type: "check". Submits documents for compliance checking against the selected case type, modifiers, and depth.

Parameters

NameTypeRequiredDescription
case_typestringYesCase type for the job (e.g. "residential_mortgage", "buy_to_let").
depthstringYesCheck depth tier: "admin_check", "soft_check", "full_check", or "mortgage_club_check".
variationsstring[]NoModifier IDs that adjust the skill set (e.g. ["debt_consolidation"]).
documentsFile[]YesOne or more documents to analyse. Supported: PDF, DOCX, XLSX, PNG, JPG.
referencestringNoYour internal case reference.
idempotency_keystringNoUnique key for safe retries.

Request

check.py
python
from curvestone import Agent
agent = 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 | red
print(f"Skills: {len(result.skills)}")
print(f"Time: {result.processing_time}")

Response

response.json
json
{
"id": "job_7kTx9mNpQ2",
"type": "check",
"status": "queued",
"reference": "CASE-2026-00451",
"created_at": "2026-02-21T14:30:00Z"
}
POST/ask

Conversational endpoint. Ask a natural-language question about a completed job, a document, or the compliance framework.

Parameters

NameTypeRequiredDescription
questionstringYesThe question to ask.
job_idstringNoReference a previous job for context-aware answers.
documentsFile[]NoOptional documents to ask about directly.

Request

ask.py
python
from curvestone import Agent
agent = 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

response.json
json
{
"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"
}
POST/monitor

Create a recurring monitor that watches for changes and optionally triggers downstream actions.

Parameters

NameTypeRequiredDescription
typestringYesMonitor type: "website_change", "criteria_update", or "regulation_change".
targetstringYesURL or resource identifier to monitor.
schedulestringYesFrequency: "hourly", "daily", "every_15_days", "weekly", "monthly".
on_findingobjectNoAction to take when a change is detected (e.g. { "then": "check" }).
webhook_urlstringNoURL for monitor event callbacks.

Request

monitor.py
python
from curvestone import Agent
agent = 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

response.json
json
{
"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.

GET/jobs/:id

Retrieve a single job by ID. Use this to poll job status or fetch completed results.

Parameters

NameTypeRequiredDescription
idstringYesThe job ID (path parameter).

Response

response.json
json
{
"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"
}
GET/jobs

List jobs with filtering and pagination. Returns most recent first.

Parameters

NameTypeRequiredDescription
statusstringNoFilter by status: "queued", "in_progress", "completed", "failed".
typestringNoFilter by job type: "check", "ask", or "monitor".
referencestringNoFilter by your internal reference.
limitnumberNoNumber of results (default 20, max 100).
starting_afterstringNoCursor for pagination (job ID).

Response

response.json
json
{
"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"
}
POST/jobs

Universal job creation endpoint. Accepts any job type (check, ask, monitor) via the type field.

Parameters

NameTypeRequiredDescription
typestringYesJob type: "check", "ask", or "monitor".
case_typestringNoCase type for the job (e.g. "residential_mortgage"). Required for check jobs.
depthstringNoCheck depth: "admin_check", "soft_check", "full_check", or "mortgage_club_check".
variationsstring[]NoModifier IDs to apply (e.g. ["debt_consolidation", "interest_only"]).
documentsFile[]NoUploaded documents for analysis. Accepts PDF, DOCX, XLSX, images.
referencestringNoYour internal case reference for tracking.
webhook_urlstringNoURL to receive job completion callback.
idempotency_keystringNoUnique key to prevent duplicate job creation.

Response

response.json
json
{
"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.

GET/config/dimensions

Retrieve the full configuration matrix: case types, modifiers, depths, and scoring modes available to your organisation.

Response

response.json
json
{
"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.

POST/webhooks

Register a webhook endpoint to receive real-time event notifications.

Parameters

NameTypeRequiredDescription
urlstringYesThe HTTPS URL to receive webhook events.
eventsstring[]YesEvents to subscribe to: "job.completed", "job.failed", "review.required", "monitor.triggered".
secretstringNoSigning secret for HMAC verification. Auto-generated if omitted.

Response

response.json
json
{
"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.

GET/billing

Retrieve current billing period usage, spend, and plan details for your organisation.

Parameters

NameTypeRequiredDescription
periodstringNoBilling period in YYYY-MM format (defaults to current month).

Response

response.json
json
{
"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
}
GET/partner/usage

Network and partner usage analytics. Returns aggregated usage across all child brokerages.

Parameters

NameTypeRequiredDescription
periodstringNoBilling period in YYYY-MM format.
group_bystringNoGroup results by: "brokerage", "broker", "case_type", or "day".

Response

response.json
json
{
"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.

StatusErrorDescription
400bad_requestInvalid parameters or missing required fields.
401unauthorizedMissing or invalid API key.
403forbiddenInsufficient permissions for this action or resource.
404not_foundResource does not exist or is not accessible.
429rate_limitedToo many requests. Back off and retry with exponential backoff.
500internal_errorServer error. Retry the request or contact support.

Error response format

error.json
json
{
"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.

PlanRate Limit
Self-Serve60 requests / minute
Enterprise300 requests / minute