API Reference
Every endpoint of the RamAIn Workflows API, from launching runs and polling status to results, events, human input, and webhooks.
All endpoints live under the versioned base URL and require a workspace API key as a Bearer token:
Base URL: https://api.ramain.ai/api/v1
Header: Authorization: Bearer rmn_live_...
Examples below use a workflow with a single required input, order_number,
which is the same shape the in-portal API docs generate for each of your
published workflows:

Launch a run
POST /automations/{workflowId}/runs
Launches a run of a Published workflow. Returns 202 Accepted
immediately; the run executes asynchronously. Requires the workflows:run
scope.
Request body
curl -X POST https://api.ramain.ai/api/v1/automations/wf_audit_zoho/runs \
-H "Authorization: Bearer rmn_live_xJ4..." \
-H "Content-Type: application/json" \
-d '{
"inputs": { "order_number": "JODL-2041" },
"metadata": { "correlation_id": "erp-58812" },
"callback_url": "https://example.com/hooks/ramain",
"idempotency_key": "erp-58812-attempt-1"
}'
Response: 202 Accepted
{
"run_id": "run_9f3ka72",
"workflow_id": "wf_audit_zoho",
"status": "pending",
"created_at": "2026-07-21T10:30:00Z",
"status_url": "https://api.ramain.ai/api/v1/runs/run_9f3ka72",
"result_url": "https://api.ramain.ai/api/v1/runs/run_9f3ka72/result",
"events_url": "https://api.ramain.ai/api/v1/runs/run_9f3ka72/events",
"queue": { "position": 0 }
}
Passwords are never inputs
Saved Passwords a workflow uses are resolved inside the workspace at runtime. They never appear in the input contract and can't be passed through the API.
Get run status
GET /runs/{runId}
Returns the run envelope. Requires the runs:read scope.
{
"run_id": "run_9f3ka72",
"workflow_id": "wf_audit_zoho",
"status": "running",
"created_at": "2026-07-21T10:30:00Z",
"status_url": "https://api.ramain.ai/api/v1/runs/run_9f3ka72",
"result_url": "https://api.ramain.ai/api/v1/runs/run_9f3ka72/result",
"events_url": "https://api.ramain.ai/api/v1/runs/run_9f3ka72/events"
}
When a run pauses for a person, the envelope carries a description of what's needed and where to answer:
{
"run_id": "run_9f3ka72",
"status": "waiting_for_human",
"human_action": "Approve the order total before submission",
"resume_url": "https://api.ramain.ai/api/v1/runs/run_9f3ka72/human-input"
}
Run statuses
Get run result
GET /runs/{runId}/result
Returns 202 while the run is still in progress. Once the run is terminal, it
returns the structured outputs, including any files the run produced.
{
"run_id": "run_9f3ka72",
"status": "completed",
"outputs": {
"zoho_order_id": "SO-00871",
"order_total": "1,240.00 EUR",
"files": [
{
"name": "order-confirmation.pdf",
"contentType": "application/pdf",
"downloadUrl": "https://api.ramain.ai/api/v1/runtime-files/f_28ab41/download?token=..."
}
]
}
}
Downloading files
Each entry in outputs.files[] carries a signed downloadUrl. The link is
self-authenticating, so no API key is needed on the download itself; you
can hand the URL to a browser, a signed-URL-aware client, or plain curl:
# Grab the first output file from the result
RESULT=$(curl -s https://api.ramain.ai/api/v1/runs/run_9f3ka72/result \
-H "Authorization: Bearer rmn_live_xJ4...")
URL=$(echo "$RESULT" | jq -r '.outputs.files[0].downloadUrl')
NAME=$(echo "$RESULT" | jq -r '.outputs.files[0].name')
curl -o "$NAME" "$URL"
Signed links expire, so download promptly after fetching the result rather than storing the URL long-term. If a link has expired, fetch the result again for a fresh one.
List run events
GET /runs/{runId}/events
Returns the public step events of a run as a JSON list. This is the endpoint to build progress UIs on: it shows what the run is doing without you having to poll the status envelope aggressively.
{
"run_id": "run_9f3ka72",
"events": [
{ "at": "2026-07-21T10:30:04Z", "type": "run.started" },
{ "at": "2026-07-21T10:30:12Z", "type": "step.completed", "step": "Look up order JODL-2041" },
{ "at": "2026-07-21T10:31:40Z", "type": "run.completed" }
]
}
Resume human input
POST /runs/{runId}/human-input
Resumes a run in waiting_for_human by answering on the caller's behalf.
Requires the workflows:run scope. Use the resume_url from the status
envelope, or build the path from the run_id.
Request body
curl -X POST https://api.ramain.ai/api/v1/runs/run_9f3ka72/human-input \
-H "Authorization: Bearer rmn_live_xJ4..." \
-H "Content-Type: application/json" \
-d '{ "response": "Approved. The total of 1,240.00 EUR is correct." }'
The run picks up where it paused, with your response injected as if a
teammate had answered in the portal. Poll the status afterwards as usual; the
run transitions back through running on its way to a terminal state.
Webhook payload
If you passed a callback_url at launch, RamAIn POSTs to it once the run
reaches a terminal state, with User-Agent: RamAIn-Workflow-API/1.0:
{
"type": "workflow.run.completed",
"run": {
"run_id": "run_9f3ka72",
"workflow_id": "wf_audit_zoho",
"status": "completed"
},
"result": {
"outputs": { "zoho_order_id": "SO-00871" }
}
}
Verify webhooks server-side
Treat the webhook as a notification, not as truth. On receipt, fetch the run's
status_urlfrom your server with your API key and act on that response.
Example receiver
A minimal receiver that verifies the notification before acting on it:
Tab: Node.js (Express)
import express from "express";
const app = express();
app.use(express.json());
const HEADERS = { Authorization: "Bearer rmn_live_xJ4..." };
app.post("/hooks/ramain", async (req, res) => {
// Acknowledge fast; do the work after responding.
res.sendStatus(200);
const runId = req.body?.run?.run_id;
if (!runId) return;
// Verify against the API instead of trusting the payload.
const status = await (
await fetch(`https://api.ramain.ai/api/v1/runs/${runId}`, { headers: HEADERS })
).json();
if (status.status === "completed") {
const result = await (
await fetch(`https://api.ramain.ai/api/v1/runs/${runId}/result`, { headers: HEADERS })
).json();
console.log("Run finished:", result.outputs);
}
});
app.listen(3000);
Tab: Python (FastAPI)
import requests
from fastapi import BackgroundTasks, FastAPI, Request
app = FastAPI()
BASE = "https://api.ramain.ai/api/v1"
HEADERS = {"Authorization": "Bearer rmn_live_xJ4..."}
def process_run(run_id: str) -> None:
# Verify against the API instead of trusting the payload.
status = requests.get(f"{BASE}/runs/{run_id}", headers=HEADERS).json()
if status["status"] == "completed":
result = requests.get(f"{BASE}/runs/{run_id}/result", headers=HEADERS).json()
print("Run finished:", result["outputs"])
@app.post("/hooks/ramain")
async def ramain_hook(request: Request, tasks: BackgroundTasks):
payload = await request.json()
run_id = payload.get("run", {}).get("run_id")
if run_id:
# Acknowledge fast; do the work after responding.
tasks.add_task(process_run, run_id)
return {"ok": True}
Retry a dead-lettered run
Runs that fail can land in a review queue. After inspecting (or fixing the underlying cause), retry them:
POST /runs/{runId}/dead-letter/retry
Errors
Keeping the contract in sync
Every published workflow's API contract mirrors its Input block: add or rename an input in the editor, publish, and the endpoint's parameters update on the next publish. The in-portal API Docs page always shows the current contract with copy-ready JSON and curl snippets.
Was this page helpful?