Curvestone

API Reference

Base URL: https://agent.curvestone.ai/api

Authentication

All requests require your API key in the X-Agent-Key header. API keys are prefixed with cs_live_ for production and cs_test_ for sandbox environments.

HeaderValueRequired
X-Agent-Keycs_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"]).
file_idsstring[]NoFile IDs from POST /files. Use this or documents, not both.
documentsobject[]NoDocuments as URLs: [{ "url": "https://...", "filename": "doc.pdf" }].
referencestringNoYour internal case reference.
webhook_urlstringNoURL to receive a callback when the job completes or fails.
require_reviewbooleanNoWhen true, job enters pending_review instead of completing immediately.
notify_emailsstring[]NoEmail addresses notified when a job requires review or fails.
brokeragestringNoOrg ID to infer domain context (for network/partner submissions).
configobjectNoOptional overrides: skill_overrides, skip_skills.
idempotency_keystringNoUnique key for safe retries. Duplicate keys return the existing job.

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
{
"job_id": "job_7kTx9mNpQ2",
"status": "queued",
"environment": "live",
"poll_url": "/jobs/job_7kTx9mNpQ2",
"stream_url": "/jobs/job_7kTx9mNpQ2/stream"
}
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
{
"job_id": "job_7kTx9mNpQ2",
"type": "check",
"status": "completed",
"triage": "amber",
"reference": "CASE-2026-00451",
"environment": "live",
"result": {
"summary": "7 skills assessed. Affordability flagged amber.",
"key_risks": ["Affordability buffer below threshold"],
"findings": [{ "skill": "Affordability", "rating": "amber", "explanation": "..." }],
"stats": { "green": 35, "amber": 5, "red": 2 }
},
"reviewed_by": "user_abc123",
"reviewed_at": "2026-02-21T14:35:00Z",
"review_notes": "Approved with note on affordability.",
"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", "pending_review", "failed", "cancelled".
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"
}

Files

Upload documents before submitting a check. Reference the returned file IDs in POST /check.

POST/files

Upload a document for later use in a check. Returns a file ID. Accepts PDF, DOCX, XLSX, PNG, JPG.

Parameters

NameTypeRequiredDescription
filebinaryYesThe file to upload (multipart/form-data).

Response

response.json
json
{
"file_id": "file_abc123",
"filename": "fact_find.pdf",
"content_type": "application/pdf",
"size_bytes": 245891,
"created_at": "2026-02-21T14:30:00Z"
}
GET/files/:id

Download a previously uploaded file by ID.

Parameters

NameTypeRequiredDescription
idstringYesThe file ID (path parameter).
DELETE/files/:id

Delete a file. Returns 204 No Content on success.

Parameters

NameTypeRequiredDescription
idstringYesThe file ID (path parameter).

Review

Human-in-the-loop review workflow. Jobs submitted with require_review: true enter pending_review status instead of completing immediately. Use these endpoints to approve or reject.

GET/jobs/:id/review

Retrieve the review context for a pending job, including the full result and triage.

Parameters

NameTypeRequiredDescription
idstringYesThe job ID (path parameter).

Response

response.json
json
{
"job_id": "job_7kTx9mNpQ2",
"status": "pending_review",
"triage": "amber",
"result": {
"summary": "7 skills assessed. Affordability flagged amber.",
"key_risks": ["Affordability buffer below threshold"],
"findings": [...]
}
}
POST/jobs/:id/review

Submit a review decision. Moves the job from pending_review to completed (approve) or rejected (reject). Supports rating modifications.

Parameters

NameTypeRequiredDescription
idstringYesThe job ID (path parameter).
decisionstringYes"approve", "reject", or "modify".
modificationsobject[]NoRating changes when decision is "modify".
reviewer_notesstringNoNotes from the reviewer.

Response

response.json
json
{
"job_id": "job_7kTx9mNpQ2",
"status": "completed",
"reviewed_by": "user_abc123",
"reviewed_at": "2026-02-21T14:35:00Z",
"review_notes": "Approved — affordability within acceptable range."
}

Streaming & Cancellation

Stream real-time progress via Server-Sent Events, or cancel a queued/in-progress job.

GET/jobs/:id/stream

Subscribe to real-time job progress via Server-Sent Events (SSE). Events include progress updates and the final result.

Parameters

NameTypeRequiredDescription
idstringYesThe job ID (path parameter).

Response

response.json
json
event: progress
data: {"status": "running", "current_step": "Analysing affordability"}
event: progress
data: {"status": "running", "current_step": "Classifying risk"}
event: job_complete
data: {"job_id": "job_7kTx9mNpQ2", "triage": "green", "status": "completed"}
POST/jobs/:id/cancel

Cancel a queued or in-progress job. Returns 409 if the job has already completed.

Parameters

NameTypeRequiredDescription
idstringYesThe job ID (path parameter).

Response

response.json
json
{
"job_id": "job_7kTx9mNpQ2",
"status": "cancelled",
"cancelled_at": "2026-02-21T14:31: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.
409conflictState conflict — e.g. reviewing a job that is not pending_review, or cancelling a completed job.
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