Connect CrewAI agents to Router One through one custom OpenAI-compatible endpoint
CrewAI is a Python framework for role-based agents that work through tasks as a crew. CrewAI runs the agent loop, task order, delegation, tools, and memory inside your application; Router One is the model endpoint those agents call. This guide uses the custom OpenAI-compatible endpoint mode that CrewAI documents, so GPT, Claude, Gemini, and Grok family models share one base URL and one key on Chat Completions, and each model request appears as a trace in Dashboard → Logs. It starts with a one-agent, one-task crew that returns plain text, then covers the environment-variable route and what to check before adding tools, structured output, or more agents.
Install CrewAI and set your credentials
CrewAI requires Python 3.10 to 3.13. Install the crewai package in that environment; the OpenAI Python SDK this setup uses is a core dependency of crewai, so no crewai[litellm] extra is needed. The shell example below is for macOS/Linux. Replace both placeholders with a Router One key and the exact ID of a current model that supports Chat Completions, keeping any prefix that is part of the ID (for example anthropic/). The ROUTER_ONE_* names belong to this example and are read explicitly by the Python file; keep the same environment active when running it. The behavior described on this page was checked against crewai 1.15.20.
python -m pip install crewai export ROUTER_ONE_API_KEY="sk-your-router-one-key" export ROUTER_ONE_MODEL_ID="<exact-model-id-from-/models>"
Configure CrewAI to use the Router One base URL
Save this as crewai_router_one.py and run python crewai_router_one.py. LLM(custom_openai=True, base_url=..., api_key=...) is the mode CrewAI documents for a custom OpenAI-compatible endpoint: it always uses the OpenAI SDK's Chat Completions path, POST /v1/chat/completions, whatever the model ID starts with. The model string is openai/ followed by the exact catalog ID. In this mode CrewAI removes exactly one leading openai/ segment and sends the remainder unchanged, so anthropic/claude-sonnet-5 or openai/gpt-5.5 reaches the gateway exactly as the catalog lists it. The agent has no tools and the task expects plain text, so the first request carries only model and messages. crew.kickoff() runs the task and returns a CrewOutput; result.raw is the model's text.
import os
from crewai import Agent, Crew, LLM, Task
llm = LLM(
model="openai/" + os.environ["ROUTER_ONE_MODEL_ID"],
custom_openai=True,
base_url="https://api.router.one/v1",
api_key=os.environ["ROUTER_ONE_API_KEY"],
)
greeter = Agent(
role="Greeter",
goal="Reply with one short greeting.",
backstory="You keep answers short and literal.",
llm=llm,
)
task = Task(
description="Reply with one short greeting.",
expected_output="One short greeting sentence.",
agent=greeter,
)
crew = Crew(agents=[greeter], tasks=[task])
result = crew.kickoff()
print(result.raw)Use environment variables instead of an LLM object
A project scaffolded with crewai create crew builds its agents from config/agents.yaml without an llm field, and CrewAI then reads the model and endpoint from the environment: MODEL selects the model, OPENAI_API_BASE (OPENAI_BASE_URL is also accepted) sets the endpoint, and OPENAI_API_KEY supplies the key. CrewAI calls load_dotenv() when it imports its LLM module; exporting the variables in the shell works in every project layout. Use the same openai/ plus catalog ID form for MODEL: when the base URL comes from these variables, CrewAI attaches it while building the LLM, recognizes that the ID is not an official OpenAI name, and takes the same custom endpoint path, so the exact ID is sent. A plain string in agents.yaml (llm: openai/anthropic/claude-sonnet-5) or Agent(llm="...") is built without that base URL and is handed to CrewAI's LiteLLM fallback instead, which is not installed by default; keep the model in MODEL or in an LLM object.
# .env in the project directory, or export the same names in your shell MODEL=openai/<exact-model-id-from-/models> OPENAI_API_BASE=https://api.router.one/v1 OPENAI_API_KEY=sk-your-router-one-key
What to check before adding tools, structured output, or more agents
Agent tools, including the delegation tools added when allow_delegation=True and anything from the crewai-tools package, are sent as function-tool schemas on the same Chat Completions request; check tool calling on the model's detail page before enabling them. Task output_pydantic or output_json and LLM(response_format=...) send a JSON-schema response_format, so check structured-output support for the exact model first. Each Agent can take its own LLM object, so one crew can mix models on one key, and every call still lands in the same trace. LLM(stream=True) keeps the same path and adds stream_options so usage arrives in the final chunk. Leave api at its default: api="responses" switches the same object to POST /v1/responses, which the gateway serves natively only for the GPT-family IDs whose detail page lists that endpoint. A crew with several agents, tasks, or retries makes several model requests, and crew.usage_metrics is CrewAI's own token tally, not a bill. Give the crew a dedicated key with maxSpend, then reconcile in Dashboard → Logs by key, time, exact model, and request_id.
Which model ID should CrewAI send?
Copy the exact model ID from /models, preserving case, hyphens, and version suffixes; do not substitute a display name. Open its detail page and match the supported API endpoints, context window, and capabilities such as tool calling to the provider and features selected in CrewAI. A catalog listing does not mean the client can use every feature of that model. Give each tool a dedicated API key with a maxSpend cap.
Which API protocol is CrewAI using?
OpenAI-compatible describes an interface format; it does not make Chat Completions (/v1/chat/completions), Responses (/v1/responses), and Anthropic Messages (/v1/messages) interchangeable. Check the installed client version, provider configuration, and actual request path against the model detail page and API compatibility fact sheet. A successful plain-text chat does not establish support for hosted tools, conversation state, or file-editing features.
Verify the CrewAI call in your request trace
Send a simple text request from CrewAI, then match its trace in Dashboard → Logs by time, model, and request_id: tokens, cost, latency, and status. Next, test streaming, tool calls, and multi-turn history separately. For failures, retain the actual request path, full error message, and request_id. If there is no matching log, check client configuration and connectivity before attributing the error to the gateway or upstream.
FAQ
Why is the model string openai/ plus the catalog ID, and why custom_openai=True?
CrewAI's rule is that every model string carries a provider prefix, and it uses that prefix to choose a client: an anthropic/ or google/ ID goes to CrewAI's own SDK path for that vendor, which expects that vendor's API and key rather than an OpenAI-compatible endpoint, and stops with an ImportError before sending anything when the matching extra is not installed. custom_openai=True overrides that choice and forces the OpenAI SDK Chat Completions path for any ID; in this mode CrewAI drops exactly one leading openai/ segment and sends the rest unchanged. Prefixing the exact catalog ID with openai/ is therefore what puts the ID on the wire as listed: for the GPT-family ID openai/gpt-5.5 you write openai/openai/gpt-5.5, because a single prefix would be consumed and the bare name gpt-5.5 would be sent. Confirm it in Dashboard → Logs: the model column should show the full catalog ID.
Do I need the crewai[litellm] extra or LiteLLM at all?
Not for this setup. The custom endpoint mode uses the OpenAI Python SDK, a core dependency of crewai, and never imports LiteLLM. LiteLLM is only CrewAI's fallback for model strings that resolve to none of its native providers, which is exactly what happens when a plain string such as llm: openai/anthropic/claude-sonnet-5 in agents.yaml has no base URL attached while CrewAI builds the LLM. If you see 'did not match any supported native provider' together with 'LiteLLM fallback package is not installed', move the model into MODEL or into LLM(custom_openai=True, ...) instead of installing the extra; the request then goes out on /v1/chat/completions with the exact ID.
Is CrewAI's own tracing (CREWAI_TRACING_ENABLED, crewai traces) the Router One trace?
No. CrewAI's tracing and telemetry are CrewAI features with their own settings, such as CREWAI_TRACING_ENABLED and the crewai traces command, and they are independent of the gateway. The Router One trace is the entry the gateway writes in Dashboard → Logs for every model request that reaches it, with model, tokens, cost, latency, status, and request_id, whether or not CrewAI tracing is on. Nothing on this page needs a CrewAI account, plan, or hosted platform: the LLM class and these variables are part of the open-source crewai package.
Which models can CrewAI use through the gateway?
Choose a current catalog model that supports both the endpoint and the features CrewAI uses. Check /models and the model detail page for the exact ID, current rates, and capabilities; a family name such as GPT or Claude is not a compatibility guarantee. Seeing a model in the picker confirms discovery, so verify an actual request too.
Models are listed, but requests fail with 400 or 404. What should I check?
Record the actual request path and error message, then check the exact model ID. A 400 can indicate invalid parameters, unsupported tools, or a model/endpoint mismatch; a 404 can indicate an incorrect path or missing resource, so it does not by itself establish that a model was retired. If the error says must be called via, use the named endpoint or select a model supported on the current endpoint. Do not add or remove /v1 or /chat/completions across all clients indiscriminately.
Does this work from Mainland China?
Yes. The gateway is reachable from Mainland China without a VPN, and the configuration is identical to the global setup.
How do I debug a 401/402/403/429?
Match the request and error message in Dashboard → Logs. For 401, check whether the key was sent and is valid; for 402, check wallet balance and maxSpend; for 403, check key permissions and access restrictions. For 429, distinguish request/token limits from upstream throttling using the error details. Keep the request_id and follow the error-codes reference.