crewaiinc/skills

call-deployed-crew

Calling a crew or flow deployed on CrewAI AMP over HTTP: the bearer token and deployment URL, GET /inputs, POST /kickoff with the {\"inputs\": {...}} body, which input keys are required ({placeholder} tokens), polling GET /status/{kickoffid} with a deadline…

Ver código-fonte
Documento original do Skill

Renderizado do repositório de origem, preservando títulos, exemplos, código, tabelas, links e imagens.

Call a Deployed Crew

How to call a crew or flow that runs on CrewAI AMP from your own code, and how to write the crew so those calls stay correct.

Verified against crewai 1.15.22 and 1.15.23 on 2026-10-01. Live-tested on CrewAI AMP and real LLMs on 2026-10-01. Run crewai version first; if the major/minor differs, re-check the version-sensitive rows with the ask-docs skill.


1. The contract on one screen

Every deployment has its own base URL (https://<your-deployment>.crewai.com) and bearer token, both shown on the deployment's page in the AMP dashboard (Status tab). crewai deploy status prints neither. All calls send Authorization: Bearer <token>.

CallBodySuccess response (live)Docs
GET /inputs-{"inputs": ["audience", "topic"]}GET /inputs
POST /kickoff{"inputs": {"topic": "...", ...}} plus optional siblings (section 4){"kickoff_id": "<uuid>"}POST /kickoff
GET /status/{kickoff_id}-run state, progress, result (section 5)GET /status
POST /resumesee section 5 - verify the body on your deployment{"kickoff_id": "<new uuid>"}POST /resume

Use GET /inputs as the readiness check; GET / may return 404.

Responses to expect (handle the documented codes too - API introduction):

RequestLive response
Wrong token401 {"detail":"Invalid or missing authentication credentials"}
No Authorization header401 {"detail":"Not authenticated"}
Crew kickoff with a required key missing, {"inputs": {}}, {} or a bare body422 {"detail":"Missing inputs: audience, topic"} - a plain string, not details.missing_inputs
Body that is not valid JSON500 Internal Server Error
GET /status/<unknown id>200 with "state": "NOT FOUND" - not 404
bash
export CREWAI_DEPLOYMENT_URL="https://<your-deployment>.crewai.com"
export CREWAI_DEPLOYMENT_TOKEN="<your-bearer-token>"

curl -s -H "Authorization: Bearer $CREWAI_DEPLOYMENT_TOKEN" "$CREWAI_DEPLOYMENT_URL/inputs"

curl -s -X POST "$CREWAI_DEPLOYMENT_URL/kickoff" \
  -H "Authorization: Bearer $CREWAI_DEPLOYMENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inputs": {"topic": "AI agents", "audience": "engineers"}}'

curl -s -H "Authorization: Bearer $CREWAI_DEPLOYMENT_TOKEN" \
  "$CREWAI_DEPLOYMENT_URL/status/<kickoff_id>"

Rules:

  • Keep the URL and token in environment variables or a secret store. Never hard-code the token, commit it, log the Authorization header, or ship it to a browser - call the deployment from your backend.
  • The dashboard shows an organization-level Bearer Token and a User Bearer Token with narrower permissions. Use the narrowest one that works for the integration.

2. Required inputs come from {placeholder} tokens

The keys a crew needs are the {name} tokens in its task description / expected_output and its agent role / goal / backstory. GET /inputs lists them for the deployed version. Read the keys from /inputs (or from your code) - never guess them.

Locally, Crew.fetch_inputs() is crewai's own scan of the same fields:

python
from crewai import Agent, Crew, Task

agent = Agent(role="{topic} researcher", goal="Explain {topic}", backstory="Writes for {audience}.")
task = Task(description="Research {topic} for {audience}.", expected_output="A summary of {topic}.", agent=agent)
print(sorted(Crew(agents=[agent], tasks=[task]).fetch_inputs()))  # ['audience', 'topic']

What crewai does with the inputs it receives (verified locally on 1.15.22-1.15.23):

You sendWhat happens
Every placeholder keyValues are substituted into agent and task text before the first LLM call
A placeholder key missingValueError: Missing required template variable 'Template variable 'audience' not found in inputs dictionary' in description - raised before any LLM call
An extra key with no placeholderSilently ignored; it never reaches a prompt
No inputs, or inputs={}Locally: no error - interpolation is skipped and the literal text {topic} reaches the model. A deployed crew rejects it with 422 (section 1)
int / float / bool / list / dict valuesStringified with str() - locally, and on a live deployment ("audience": 3 produced AUDIENCE=3). The API documents every input value as a string - send strings to a crew (flows: section below)

Consequences:

  • Validate the request against /inputs in your client before calling /kickoff, and send every key. Do not rely on an empty-inputs run failing loudly - it does on AMP, not when you call kickoff() yourself.
  • A literal JSON example inside a prompt (Reply like {"title": "..."}) is not interpolated, but fetch_inputs() reports "title": "..." as an input name. Any input list derived from braces will ask for it. Put output structure in output_pydantic / response_model instead of a JSON sample in the text.
  • Send dates as ISO 8601 strings and structured values as JSON strings, and parse them inside the crew.

Flows are different. A deployed flow's GET /inputs lists the fields its state model declares - {"inputs": ["topic", "count", "id", "history"]} for a test model that declared id itself; a model that leaves id to the Flow does not list it (see the build-flow skill) - and none of them is required: a flow kickoff with {"inputs": {}} returned 200 and ran on default state. Send only the fields you mean to set, with the JSON types of the state model (a list as a list - "[]" as a string made the run FAILED), and never send id (section 3).


3. The kickoff body: inputs is a wrapper

BadGood
{"topic": "AI agents", "audience": "engineers"}{"inputs": {"topic": "AI agents", "audience": "engineers"}}
{"inputs": {"topic": "...", "meta": {...}}}{"inputs": {"topic": "..."}, "meta": {...}}
{"inputs": {"id": "<previous-kickoff-id>", "topic": "..."}} (flows, deprecated){"inputs": {"topic": "..."}, "restoreFromStateId": "<previous-kickoff-id>"}
Retrying POST /kickoff after a read timeoutTreat it as "maybe started"; look for the run before sending another kickoff

What a bare body does, live: a crew answers 422 {"detail":"Missing inputs: audience, topic"} (the same as sending no inputs). A flow answers 200, ignores the top-level keys and runs on default state - nothing tells you the values were dropped.


4. Optional fields beside inputs

All of these sit at the top level of the kickoff body, never inside inputs:

FieldPurposeDocs
metaFree-form metadata ({"requestId": "..."}); echoed back in task and crew webhooks and in /status last_executed_task.metaPOST /kickoff
taskWebhookUrlPOSTed after each task completessame
stepWebhookUrlCalled after each agent thought/actionsame
crewWebhookUrlPOSTed when the execution completessame
humanInputWebhookUrlPOSTed when a human_input=True task waits for review (section 5)HITL Workflows
webhooksEvent streaming: {"events": [...], "url": "...", "realtime": false, "authentication": {"strategy": "bearer", "token": "<your-webhook-secret>"}}Webhook Streaming
restoreFromStateIdFlows with @persist: hydrate state from a previous run, record a new runinputs.id deprecation

What arrived at a receiver in a live test:

WebhookPayloadAuthorization header
taskWebhookUrlOne POST per task: name, description, expected_output, agent, output, output_json, summary, kickoff_id, metanone
crewWebhookUrlOne POST on success: kickoff_id, meta, result, result_json, token_usage. Not sent for a run that failed in @before_kickoffnone
stepWebhookUrlNothing, for a crew whose agent had no tools-
webhooks (streaming){"events": [{id, execution_id, timestamp, type, data}]}; realtime: false batched 4 events into one POST, realtime: true sent one POST per event; crew_kickoff_failed arrived for a failed runBearer <your-webhook-secret>
  • The task / crew / human-input webhooks carry no secret. Treat them as a hint: match the kickoff_id against ones you issued, then confirm with GET /status before acting.
  • Streamed events arrived out of order (a task_started before the previous task_completed). Sort by timestamp or data.emission_sequence. Deduplicate on execution_id + id. Reject deliveries without your bearer secret.

Store the kickoff_id with your own request id as soon as /kickoff returns. It is the only handle you have on that run.

For a flow with @persist, the run's state.id is its kickoff_id, so restoreFromStateId takes a previous kickoff_id. Live: restoring from run 1 gave count=2 history=['one','two'] under a new state id; an unknown id ran from defaults with no error. The deprecated {"inputs": {"id": "<kickoff_id>"}} also restored the state but wrote it back under the old id.


5. Polling /status/{kickoff_id}

Live deployments return the Kickoff Crew guide shape. The API reference shows another shape; a client that reads both costs nothing:

SourceState fieldValuesResult
Live (crew and flow)state; status is a message ("Task is Running", the exception text on FAILED, null on SUCCESS)seen: PENDING, STARTED, RUNNING, SUCCESS, FAILED, NOT FOUNDresult string (null until done), result_json (null for plain text)
Kickoff Crew guidesamealso lists PAUSED, REVOKEDsame
API referencestatusrunning, completed, errorresult.output, error text in error

Rules:

  1. Read state; fall back to status only when state is absent. Compare lower-cased.
  2. Terminal: success = success / completed; failure = failed / error / revoked; paused = waiting for human feedback. Anything else means keep polling.
  3. Read result_json, then result (string, or result.output in the reference shape). A failed run has result: null; the reason is in status (for example "ValueError: ...").
  4. Poll with backoff (start around 2 s, grow to about 15 s, add jitter) under an overall deadline you choose from the crew's normal run time. Never poll in an unbounded while True.
  5. A deadline is not a failure of the run. Report it with the kickoff_id, keep the id, and check again later - do not kick off a duplicate.
  6. Crews report progress.unit: "task" with completed / total. Flows report unit: "step" with total and remaining null; show completed as a count.
  7. An unknown id comes back as HTTP 200 with state: "NOT FOUND" (the docs say 404; handle both). Check you are polling the deployment that issued the id; tolerate a few right after kickoff, then fail. A crew waiting for human input also reads NOT FOUND (below).
  8. If /status keeps answering 5xx, the run itself may have failed: stop after a few tries and check the deployment's logs instead of polling to the deadline.

Prefer webhooks (section 4) over tight polling when many runs are in flight.

Human input and POST /resume - verify before relying on it

Before building on crew human_input=True over the API, verify the whole cycle on your own deployment: pass humanInputWebhookUrl (it is the only signal that a run is waiting; its body carries execution_id and a UUID task_id), handle NOT FOUND as well as PAUSED from /status while waiting, check which field casing (executionId/taskId or snake_case) your deployment accepts, and confirm the resumed run reaches SUCCESS.

For new work, consider a Flow with @human_feedback (Human Feedback in Flows). The docs say webhook URLs are not carried over to /resume; send them again.


6. The first call

Do not assume the first request answers as fast as the rest, or that it answers 200 on the first try. Design for it:

  • Give the first request a generous timeout (minutes, not seconds) and retry 502 / 503 / 504 with backoff, honoring Retry-After.
  • Make that first request GET /inputs: it is read-only, so retrying it is always safe, and you need its key list anyway.
  • Retry POST /kickoff only when the request provably never reached the server (connection refused / DNS) or got a 503. A read timeout, 502 or 504 may mean the run started: look for it before sending another kickoff.

In the live tests, GET /inputs answered 200 in about 0.1-0.25 s right after each deployment showed Online, and no 5xx was seen. That is one sample, not a promise - keep the defensive settings.


7. A small Python client

The full client (httpx, about 200 lines) is in references/python-client.md. It does sections 2-6: validates inputs against /inputs, wraps them in {"inputs": ...}, stringifies values, retries only what is safe, polls with backoff under a deadline, and raises RunFailed / RunPaused / RunTimeout carrying the kickoff_id.

python
import os
from deployed_crew_client import DeployedCrew, RunTimeout

crew = DeployedCrew(os.environ["CREWAI_DEPLOYMENT_URL"], os.environ["CREWAI_DEPLOYMENT_TOKEN"])
try:
    result = crew.run({"topic": "AI agents", "audience": "engineers"}, deadline_s=900)
except RunTimeout as e:
    print("still running, check later:", e.kickoff_id)
finally:
    crew.close()

For a flow, call crew.run(inputs, flow=True): its /inputs lists optional state fields, and its values must keep their JSON types (section 2). All of this was run against live deployments on 2026-10-01: success, RunFailed with the run's exception text, client-side MissingInputsError, server 422, 401 without the token in the message.

A curl polling loop with a deadline is in the same reference.


8. Write the crew so repeated and concurrent calls stay correct

Your deployment serves many kickoffs. Do not assume each one gets fresh Python objects: module globals, class attributes, tool instances and anything cached at import time can outlive a run and be visible to the next run, or to one running at the same time. The first three rows were reproduced locally on 1.15.22-1.15.23 (see references/concurrency-safe-crews.md).

Do notWhyDo instead
Write to task.description (or agent role/goal/backstory) at runtime, e.g. in @before_kickoffcrewai keeps the first-seen text as the template and re-interpolates from it. Two runs on one crew object: run 2's prompt carried run 1's customer and not its ownPut per-run values in {placeholders} and pass them as inputs; @before_kickoff may add or normalise inputs and return them
Keep per-run data on a tool instance (self.seen, self.customer)crew.copy() (used by kickoff_for_each) reuses the same tool objects, so run 2 saw run 1's dataKeep tools stateless; pass per-run values as tool arguments, or read them from a contextvars.ContextVar set in @before_kickoff
Run two kickoffs at once on one Crew objectOverlapping akickoff() calls: the second raised RuntimeError: Executor is already running. Cannot invoke the same executor instance concurrently. Overlapping kickoff_async() calls raised it in some runs and not others.When you host crews yourself, build a fresh crew per request: ResearchCrew().crew().kickoff(inputs=...)
Store results, quotas, or "already processed" flags in memory or on local diskThey vanish on restart and are not shared between workersUse storage you own (database, object store) and pass ids in inputs
Call input() in a deployed crewNobody is at a terminal to answer it, so the run cannot finishA Flow with @human_feedback, or human_input=True + POST /resume once you have proved it works (section 5)

In a live test, 8 concurrent kickoffs to one deployed crew each returned their own inputs in the output and in last_executed_task, with no cross-over. That shows the platform did not mix those runs; it does not make the patterns above safe - keep them.

Stream or chunk large inputs (large files, big dataframes, large knowledge bases) instead of loading them whole, and measure peak memory locally before deploying.

Shared external limits still apply across concurrent runs: enforce per-minute API limits by waiting, and claim any daily quota in shared storage under a lock.


9. Common mistakes

SymptomCauseFix
Crew: 422 {"detail":"Missing inputs: audience, topic"} on every callBody is {"topic": ...} without the inputs wrapperSend {"inputs": {...}}
Flow ignores the values you sent and runs on defaultsSame bare body - a flow accepts it with 200Send {"inputs": {...}}
422 listing one keyA {placeholder} in agent or task text has no matching inputSend every key from GET /inputs; check for typos and stray braces
Client refuses a flow kickoff: missing count, history, ...Flow /inputs lists every declared state field, none requiredDo not enforce /inputs for flows; never send id
500 Internal Server Error on kickoffBody is not valid JSONBuild the body with a JSON library or jq, not string concatenation
Locally: Missing required template variable '...' in descriptionSame as above, raised by crewai before any LLM callSame
Output talks about {topic} literallyKicked off with no / empty inputs - interpolation was skipped, no errorValidate inputs client-side; never send {} to a crew with placeholders
/inputs lists a key like "title": "..."A JSON sample in a prompt is read as a placeholderRemove JSON samples from prompts; use output_pydantic / response_model
401Wrong token, or a token from another deploymentCopy the token from this deployment's Status tab
Poll loop never endsWaiting for one exact string ("completed") while the API returns SUCCESS, or no deadlineLower-case, accept both vocabularies, add a deadline
Every poll says "state": "NOT FOUND"Wrong deployment URL for that id, or a crew waiting for human inputCheck the URL; for HITL see section 5
GET / returns 404The live deployment has no root routeUse GET /inputs as the health check
Result is empty / NoneReading result before the state is terminal, or ignoring result_jsonRead the result only after a success state; check result_json first
Duplicate runsPOST /kickoff retried after a read timeout, 502 or 504Retry POST /kickoff only on connection failures and 503
First call times out, the next one worksClient timeout too short for a slow first responseLong first-call timeout, retries with Retry-After, start with GET /inputs
A run's prompt shows another run's customer or topicPer-run data written into task text, tool instances, or globalsSection 8
Webhook events arrive out of orderHTTP delivery is not orderedSort by timestamp or data.emission_sequence
/resume returns 422 "executionId ... Field required"Field casing differs from the reference pageSee section 5 and test the whole pause/resume cycle first

10. Checklist

  • [ ] Base URL and token come from env vars / a secret store; the token never reaches logs or a browser
  • [ ] Client calls GET /inputs first and refuses to kick off a crew with a missing key (not a flow)
  • [ ] Kickoff body is {"inputs": {...}} with string values; meta, webhook fields and restoreFromStateId sit beside inputs
  • [ ] Webhook receivers check the bearer secret (streaming) or confirm the kickoff_id with /status (task / crew webhooks carry no secret)
  • [ ] kickoff_id is stored with your request id immediately
  • [ ] Polling reads state then status, lower-cased, handles success / failure / paused / NOT FOUND / repeated 5xx, uses backoff and an overall deadline
  • [ ] A deadline is reported with the kickoff_id, not retried as a new kickoff
  • [ ] First request has a long timeout and retries 502/503/504 honoring Retry-After
  • [ ] No runtime writes to task or agent text; per-run data flows through inputs
  • [ ] Tools and module globals hold no per-run state
  • [ ] Large inputs are streamed or chunked, and peak memory was measured locally before deploying

References

  • Python client - the full httpx client, usage, and a curl polling loop with a deadline
  • API contract - every endpoint, field, state and error with its public doc link
  • Concurrency-safe crews - the shared-state failures reproduced locally and the patterns that avoid them

For related skills:

  • deploy-to-amp - getting the project onto AMP in the first place
  • build-flow - @persist, restore_from_state_id, @human_feedback
  • design-task - writing task text with {placeholders}
  • test-crewai-project - running crews offline with a stub LLM to test inputs and prompts
  • ask-docs - query the live docs when a field here looks out of date
do mesmo repositório

Mais Skills

Todos os Skills
crewaiinc
Comunidade

ask-docs

Query the official CrewAI documentation for answers. Use when the user has a CrewAI question that isn't fully covered by the getting-started, design-agent, design-task skills — e.g., specific API details, configuration options, advanced features, troubleshooting errors, enterprise features, tool references, or anything where the latest docs are the best source of truth.

instalações
2
GitHub Stars
45
Atualizado
6 de out.
crewaiinc
Comunidade

build-flow

Building CrewAI Flows on crewai 1.15.x: structured (Pydantic) vs dict state and state.id, @start/@listen/@router wiring, or/and semantics, router labels vs method names, @persist (correct import, where it stores state, restorefromstateid), checkpointing with CheckpointConfig, @humanfeedback and HumanFeedbackResult.feedback, plot(), calling crews and agents from flow methods, kickoff inputs, and the crewai create flow scaffold. Use when writing or debugging a Flow subclass or main.py with from cr

instalações
2
GitHub Stars
45
Atualizado
6 de out.
crewaiinc
Comunidade

check-crewai-api

Current crewai 1.15.x API versus the 0.x API that coding assistants remember: imports, Agent/Task/Crew parameters and defaults, kickoff variants, CrewOutput fields, LLM model strings and provider extras, structured output (outputpydantic, responsemodel, responseformat), guardrails, unified Memory, knowledge embedders, the crewai create crew wizard vs --classic, reset-memories flags, and removed features. Use when writing, reviewing, or debugging any crewai code, before trusting remembered crewai

instalações
2
GitHub Stars
45
Atualizado
6 de out.
crewaiinc
Comunidade

connect-tools-and-mcp

Giving crewAI agents tools and MCP servers on crewai 1.15.x: custom BaseTool with argsschema and @tool, which crewaitools names really exist, tool caching, maxusagecount, ToolFailure, Agent(mcps=[...]) string and MCPServerStdio/HTTP/SSE forms, rewritten MCP tool names, MCPServerAdapter, timeouts, and what works on a hosted deployment. Use when writing or debugging a tool or MCP connection, when you see 'cannot import name BaseTool from crewaitools', 'You are missing the mcp package', 'MCPConnect

instalações
2
GitHub Stars
45
Atualizado
6 de out.