Approvals
By default the agent decides everything itself. Turn that off when you want a human, or your own code, to approve the plan before credits are spent.
Unattended by default
Runs are unattended unless you ask otherwise. The agent approves its own plan, resolves ambiguity with the most reasonable assumption, and runs straight through to a terminal status. Start it, poll it, read the result. There is nothing to answer.
Your prompt is the only chance to steer the run. Be specific about duration, aspect ratio, tone and which file goes where.
Two things still end an unattended run early. No assumption can stand in for either one:
reason | What happened |
|---|---|
needs_upgrade | The edit required a paid capability your plan does not include. retryable is false. Upgrading is the only fix. |
unexpected_interrupt | The agent hit a question it could not answer for itself. Rare. Usually the prompt left something essential undecided. |
Both arrive as status: "failed" with that reason set, not as a pause.
Interactive runs
Send "interactive": true to approve the plan before credits are spent, or to answer the agent’s questions yourself. Omit it for the unattended behavior above.
An interactive run pauses at status: "waiting_input" and carries an interrupt describing the question:
{
"status": "waiting_input",
"reason": "waiting_input",
"interrupt": {
"id": "int_abc...",
"type": "plan_review",
"summary": "Generate music, transcribe the interview, add captions.",
"cost_credits": 42,
"options": [
{ "label": "Approve plan", "value": "approve" },
{ "label": "Reject", "value": "reject" }
],
"allow_input_text": true
}
}
#Answer a paused job
https://api.rendley.com/v1/agent/jobs/{jobID}/respondAn empty response means approve, so {} is a valid body.
status | Meaning |
|---|---|
resuming | Accepted. The run continues; keep polling. |
not_waiting | The job was not paused. Nothing happened. |
invalid_response | The answer was not one of the offered options and the pause does not accept free text. The job stays paused; answer again. |
An answer is matched against the interrupt’s options by value or label, case-insensitively. Free text is accepted only when allow_input_text is true.
reject does not always abort. On a plan or tool review it means “skip this and carry on”, so the agent still composes what you already paid for. On other pause types it stops the run.
This endpoint applies to interactive runs only. An unattended run never reaches waiting_input.
Don't have one? Create an API key
One of the interrupt's option values, or free text when allow_input_text is true. Empty means approve.
const res = await fetch("https://api.rendley.com/v1/agent/jobs/{jobID}/respond", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({
"response": "approve"
}),
});
const { data } = await res.json();curl -X POST "https://api.rendley.com/v1/agent/jobs/{jobID}/respond" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"response": "approve"
}'import requests
res = requests.post(
"https://api.rendley.com/v1/agent/jobs/{jobID}/respond",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"response": "approve"
},
)
data = res.json()["data"]$ch = curl_init("https://api.rendley.com/v1/agent/jobs/{jobID}/respond");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_KEY", "Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["response" => "approve"]));
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true)["data"];package main
import (
"fmt"
"io"
"net/http"
"bytes"
)
func main() {
body := bytes.NewBufferString(`{
"response": "approve"
}`)
req, _ := http.NewRequest("POST", "https://api.rendley.com/v1/agent/jobs/{jobID}/respond", body)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
b, _ := io.ReadAll(res.Body)
fmt.Println(string(b))
}require "net/http"
require "json"
uri = URI("https://api.rendley.com/v1/agent/jobs/{jobID}/respond")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer YOUR_API_KEY"
req["Content-Type"] = "application/json"
req.body = { response: "approve" }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)["data"]Response
{
"data": {
"status": "resuming",
"job": {
"job_id": "c41d8f2b-7a63-4e09-bd15-8f3a29c7e604",
"status": "running"
}
}
}#Cancel a running edit
https://api.rendley.com/v1/agent/jobs/{jobID}/cancelCancels a job that is pending, running, or paused. The response is the job in its final state. A job that already finished is returned unchanged.
Don't have one? Create an API key
const res = await fetch("https://api.rendley.com/v1/agent/jobs/{jobID}/cancel", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const { data } = await res.json();curl -X POST "https://api.rendley.com/v1/agent/jobs/{jobID}/cancel" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
res = requests.post(
"https://api.rendley.com/v1/agent/jobs/{jobID}/cancel",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = res.json()["data"]$ch = curl_init("https://api.rendley.com/v1/agent/jobs/{jobID}/cancel");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_KEY"]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true)["data"];package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.rendley.com/v1/agent/jobs/{jobID}/cancel", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
b, _ := io.ReadAll(res.Body)
fmt.Println(string(b))
}require "net/http"
require "json"
uri = URI("https://api.rendley.com/v1/agent/jobs/{jobID}/cancel")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer YOUR_API_KEY"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)["data"]Response
{
"data": {
"job_id": "c41d8f2b-7a63-4e09-bd15-8f3a29c7e604",
"status": "canceled",
"reason": "canceled"
}
}