CrewAI ToolFailure: report a paid API error
Return a ToolFailure from a CrewAI tool when Sume refuses a job, so the run records a failure instead of a success, and set the policy to raise.

When a CrewAI tool calls a paid API and the API refuses, return a ToolFailure instead of an error string. CrewAI's docs say a tool call that returns error text "worked", so the agent narrates the problem and the run is recorded as a success; a returned ToolFailure is recorded as a failure, and with tool_failure_policy set to raise it aborts the run.
The CrewAI facts come from its Tools page, read 2026-09-29. The Sume error codes come from Errors and rate limits and Generation admission. Sume has no CrewAI connector; the tool below is a plain HTTPS call.
How do I return a ToolFailure from a Sume call?
The docs show ToolFailure(message=..., code=..., retryable=...) returned from _run. Sume's error body has an error object with code, message, and request_id, so map those across. The attempt argument goes into the Idempotency-Key, which lets a retry after a capacity refusal use a new key.
import hashlib, os, requests
from crewai.tools import BaseTool
from crewai.tools.tool_failure import ToolFailure
RETRYABLE = {"rate_limited", "queue_full", "provider_capacity_exceeded"}
class StartVideo(BaseTool):
name: str = "start_video"
description: str = "Start a paid Sume video job. Returns the job id."
def _run(self, prompt: str, attempt: int = 1):
key = hashlib.sha256(prompt.encode()).hexdigest()[:16]
r = requests.post(
"https://api.sume.com/v1/videos", timeout=30,
json={"model": "sume/auto", "prompt": prompt},
headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
"Idempotency-Key": f"crew-{key}-{attempt}"})
if r.ok:
return r.json()
err = r.json().get("error", {})
code = err.get("code", str(r.status_code))
return ToolFailure(
message=f"Sume refused the job ({code}, {err.get('request_id')}): {err.get('message')}",
code=code, retryable=code in RETRYABLE)Which Sume errors are worth retrying?
The retryable flag is yours to set. This split follows Sume's error docs:
| Status | Code | Retryable? | What to do |
|---|---|---|---|
| 400 | invalid_request | No | Fix the request |
| 401 | unauthorized | No | Fix the API key |
| 402 | insufficient_credits | No | Add funds or lower the request cost |
| 409 | idempotency_conflict | No | Reuse a key only for an exact retry |
| 429 | rate_limited | Yes | Back off; use retry-after when present |
| 429 | queue_full | Yes, later | Wait for jobs to finish or cancel queued jobs |
| 503 | provider_capacity_exceeded | Yes, later | Retry later |
Should the retry reuse the Idempotency-Key?
For an exact retry after a timeout, yes: the same key and body returns the original job. For queue_full and provider_capacity_exceeded the docs say to retry with the same key, but in current code the API rethrows the stored refusal of a failed job, so a same-key retry can replay the refusal. Treat that as current behavior and wait until the cause clears, then send a new key, which is why the sketch takes attempt.
How do I make a failure stop the crew?
The default policy is warn: it records the failure, emits ToolFailureDetectedEvent, and continues. raise records and emits, then aborts with ToolExecutionFailedError, and ignore does nothing. The most specific setting wins in the order tool, task, agent, crew, then warn, so you can set ToolFailurePolicy.RAISE on only the task that spends money.
Otherwise check result.has_tool_failures after crew.kickoff(). The docs say a crew can finish successfully with a non-empty tool_failures list, so check it before treating the output as complete.
What about a job that fails after it starts?
A submit that returns 202 is not a finished video. A job read is a normal response whose status can be failed; Sume's docs say to check the error field. Give the read tool the same shape: return the job body while it is pending or in_progress and completed, and a ToolFailure for failed, so the crew records it.
Sources
Related posts
More in Integrations
- C# HttpClient POST JSON with a Bearer token
Set Authorization with AuthenticationHeaderValue("Bearer", key), send a JSON body, then read the status and body before EnsureSuccessStatusCode.
- Cursor mcp.json to Claude Code: why a url entry fails
A url entry copied from Cursor's mcp.json fails in Claude Code until it has type http. Here is the claude mcp add-json command for Sume's hosted MCP server.
- Cursor MCP OAuth redirect URL: which one to allow
Cursor uses fixed OAuth redirect URLs for MCP servers, one for web and Cursor Agents and one for the desktop app. Here is what each is and how Sume treats them.
- Cursor team MCP servers: share Sume's hosted MCP across a team
Cursor's docs describe project, global and team MCP servers. Where Sume's hosted MCP fits in each, how sign-in works per person, and what a shared key changes.
Written by Sume