Pipedream and any HTTP tool
Anything that can send an HTTP request can use Curva: Pipedream, Power Automate, Retool, Airtable automations, a webhook in your own app. There is one call to make and one to send feedback.
The contract in four lines
Section titled “The contract in four lines”POST {base}/v1/decide Authorization: Bearer curva_… Content-Type: application/json {"state": <any JSON>, "questions": {<key>: <question>}, "project": "…"}→ {"id": "dec_…", "answers": {<key>: {"choice" | "score" | "noul" | "selected", "confidence", "abstain", …}}}POST {base}/v1/feedback {"decision_id": "dec_…", "question": <key>, "label": <true answer>}Question types, options and every response field are in the HTTP API reference.
The OpenAPI spec at GET /openapi.json describes it for tools that import one.
Routing pattern
Section titled “Routing pattern”Every tool has some branch step (Paths, Router, Switch, If). Branch in this order:
answers.<key>.abstainistrue→ a human task. It is set only when the question hasmin_confidence, so set it on any question that routes work.answers.<key>.choiceequals an option key → that route.none_of_theseis a possible choice (the escape option) unless the question sets"escape": false; send it to a human too.- A Noul answer is P(yes): compare
answers.<key>.noulwith a threshold (for example> 0.9).
Keep id next to what you created; it is how feedback finds the decision later.
Pipedream
Section titled “Pipedream”In a workflow, add a Node.js code step after the trigger. Put the server URL and key in
environment variables (CURVA_BASE_URL, CURVA_API_KEY):
export default defineComponent({ async run({ steps, $ }) { const res = await fetch(`${process.env.CURVA_BASE_URL}/v1/decide`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.CURVA_API_KEY}` }, body: JSON.stringify({ project: "support", state: { ticket: steps.trigger.event.body.text }, questions: { department: { type: "choice", instructions: "Which team should handle this", options: { billing: "Payments, refunds", technical: "Bugs" }, min_confidence: 0.8 } } }) }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status} ${data.error.type}: ${data.error.message}`); return data; }});Later steps read steps.curva.$return_value.answers.department.choice (name the step curva).
Branch with If/Else or Switch on it, following the routing pattern above. Feedback is the
same fetch to /v1/feedback.
Any other tool
Section titled “Any other tool”- Use the tool’s HTTP or webhook action with method
POST, a raw JSON body and the two headers. - Build the body with the tool’s JSON helper (or a code step) rather than pasting mapped text into a JSON template: quotes and newlines in the text would break it.
- A
429carriesRetry-After(seconds); retry after it. A502means no model could answer; retry later. Other 4xx errors name the problem inerror.message.

