---
title: 'Quickstart'
description: 'An agent that runs in camelRun, calls a tool in your own process, and answers you'
---
[View as Markdown](https://camelai.com/docs/camelrun/quickstart.md) · [Docs index for coding agents](https://run.camelai.com/llms.txt)

> **Tip:**
>
> **Set up with your coding agent.** Paste this into Claude Code, Codex, Cursor
> or any coding agent. It asks you what the agent should do, installs the SDK,
> connects your account without pasting keys into chat, and runs your first
> agent.
>
> ```text
> Read https://run.camelai.com/SKILL.md and set up camelRun in this project. Ask me what the agent should do.
> ```

In five minutes you will have an agent that runs in camelRun, calls a tool in
your own process, and answers you. camelRun keeps the model loop, the agent's
history and its files. Your tools stay in your code, with your credentials.

## Credit

Signing in to the [console](https://run.camelai.com/console) with GitHub or
Google creates your account. GitHub accounts older than 30 days start with free
credit. Everyone else verifies a card on the
[Billing](https://run.camelai.com/console/billing) page to unlock starting
credit: the card is saved, not charged. Runs use credit even when you bring
your own provider key.

## Try it from your coding agent (no key)

Connect the hosted MCP server and sign in. There is no key to create or copy.

### Add the camelRun MCP server

```bash Claude Code
claude mcp add --transport http camelrun https://run.camelai.com/mcp
# then run /mcp in Claude Code and sign in with GitHub or Google
```

```bash Codex
codex mcp add camelrun --url https://run.camelai.com/mcp
codex mcp login camelrun
```

Cursor, VS Code and Claude: see [Connect your coding agent](/docs/camelrun/cli#connect-your-coding-agent).

### Ask for an agent

```text
Use the camelrun MCP server to create my first camelRun agent: ask me what it should do, create it, run it once, and show me its reply.
```

Your agent appears under **Agents** in the
[console](https://run.camelai.com/console).

## Build it into your app

### Get an API key

Sign in to the [console](https://run.camelai.com/console). Under
**API tokens**, create a token and export it:

```bash
export CAMELAI_API_KEY=art_...
```

The key can create and control every agent in your account. Keep it on
your server and never ship it to a browser.

### Install the SDK

```bash TypeScript
# Node 22 or later, or Bun
npm install @camelai/run
```

```bash Python
# Python 3.11 or later
pip install camelai-run
```

### Run an agent

```ts quickstart.mts
import { Agents, schema, tool } from "@camelai/run";

const agents = new Agents(); // reads CAMELAI_API_KEY

// A tool is an ordinary function. It runs here, in your process.
const weather = tool({
  description: "Today's weather in a city",
  input: schema.Object({ city: schema.String() }),
  execute: ({ city }) => ({ city, forecast: "sunny", highC: 24 }),
});

// The same key is the same agent, with its history, every time you run this.
const agent = await agents.upsert("quickstart", {
  instructions: "You are a concise assistant.",
  tools: { weather },
});

const run = await agent.run("Should I bring an umbrella in Lisbon today?");
console.log(run.text);

await agents.close();
```

```python quickstart.py
import asyncio
from camelai_run import Agents, tool

@tool
def weather(city: str) -> dict:
    """Today's weather in a city"""
    return {"city": city, "forecast": "sunny", "highC": 24}

async def main():
    async with Agents() as agents:  # reads CAMELAI_API_KEY
        agent = await agents.upsert(
            "quickstart",
            instructions="You are a concise assistant.",
            tools=[weather],
        )
        run = await agent.run("Should I bring an umbrella in Lisbon today?")
        print(run.text)

asyncio.run(main())
```

Run it with `npx tsx quickstart.mts` or `python quickstart.py`.

`.mts` makes the TypeScript an ES module, which top-level `await` needs; in
a project with `"type": "module"`, `.ts` works too. The Python SDK is
async-only: call it from `async` code, and from synchronous code (a script,
a Django view, a Celery task) with `asyncio.run(...)`.

The examples name no model, so the agent gets your account's default: a model
your account can use.

### Choose a model (optional)

List the models your account can use, then name one:

```bash CLI
npx -y @camelai/camelrun models --available
```

```bash curl
curl -s "https://run.camelai.com/v1/models?available=true" \
  -H "Authorization: Bearer $CAMELAI_API_KEY" | jq -r '.[].id'
```

```ts TypeScript
const agent = await agents.upsert("quickstart", {
  model: "<a model from the list>", // e.g. provider/model-id
  instructions: "You are a concise assistant.",
  tools: { weather },
});
```

```python Python
agent = await agents.upsert(
    "quickstart",
    model="<a model from the list>",  # e.g. provider/model-id
    instructions="You are a concise assistant.",
    tools=[weather],
)
```

If a run fails with code `model_key_missing`, the agent names a model your
account has no key for, and the error says which key to set: pick a model from
the list, leave `model` out, or add the provider's key under **Models & keys**.

## With curl

Without an SDK, an agent can't call tools in your process, but everything else
works over REST. Create an agent with a first prompt, then poll the run:

```bash
BASE=https://run.camelai.com; AUTH="Authorization: Bearer $CAMELAI_API_KEY"

# The Idempotency-Key is the agent's key: the same key is the same agent.
CREATED=$(curl -s $BASE/v1/agents -H "$AUTH" -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-curl" \
  -d '{"systemPrompt": "You are a concise assistant.",
       "prompt": {"text": "Write a haiku about durable agents."}}')
AGENT=$(echo "$CREATED" | jq -r .id); REQ=$(echo "$CREATED" | jq -r .prompt.id)

# ?wait=25 answers as soon as the run ends (or after 25 s, still running: ask again).
until RUN=$(curl -s "$BASE/v1/agents/$AGENT/requests/$REQ?wait=25" -H "$AUTH") && jq -e '.state == "completed"' <<< "$RUN" > /dev/null; do :; done
jq -r 'if .status == "failed" then "failed: \(.error)" else .outcome.result.reply end' <<< "$RUN"
```

Send later messages with `POST /v1/agents/{id}/prompt {"text": "..."}`, which
answers `202` with the request to poll. See [REST API](/docs/camelrun/api).

## Stream the reply

`run()` waits for the whole run. To show it as it happens, `stream()` yields
text as the model writes it, each tool call and result, and the finished run
last:

```ts TypeScript
for await (const part of agent.stream("And tomorrow?")) {
  if (part.type === "text") process.stdout.write(part.text);
  if (part.type === "tool_call") console.log(`\n[${part.name}(${JSON.stringify(part.arguments)})]`);
  if (part.type === "done") console.log(`\n(${part.run.status})`);
}
```

```python Python
stream = agent.stream("And tomorrow?")
async for part in stream:
    if part.type == "text":
        print(part.text, end="", flush=True)
    elif part.type == "tool_call":
        print(f"\n[{part.name}({part.arguments})]")
    elif part.type == "done":
        print(f"\n({part.run.status})")
run = await stream.result()  # the same run as "done" has, also after a stream that broke off
```

The run's result is the source of truth, and the stream is for display. If a
stream breaks, the run isn't lost: `run()` (or `stream.result()`) still
resolves with it.

## What just happened

* `upsert("quickstart", ...)` made a keyed agent, or found the one from last
  time and brought it to this configuration. It lives until you delete it, so
  running the file again continues the same conversation.
* Because the agent has `tools`, this process served them. camelRun called
  `weather` here, over the connection the SDK holds. One process at a time
  serves an agent's tools, and only while it runs. Serverless functions,
  several web workers or task workers, and multi-user backends serve tools over
  HTTP instead, which also keeps them working through deploys; see [Several
  processes, workers and deploys](/docs/camelrun/tools#several-processes-workers-and-deploys).
* `run()` resolved with a `Run`: `status` (`completed`, `input_required` or
  `failed`), `text`, `inputs`, `error`, `toolErrors` and `toolCalls` (Python
  `tool_errors`, `tool_calls`). A failed run throws a `RunError` unless you pass
  `throwOnError: false` (`throw_on_error=False`). A `completed` run can still
  list tool errors, e.g. `connection_lost` when the process serving its tools
  went away mid-call: check them.
* `agents.close()` let the process exit. The agent stays in camelRun.

## Next steps

### [Concepts](/docs/camelrun/concepts)

Keyed agents, runs, events, and where tools run.

### [Build a chat app](/docs/camelrun/chat-app)

Put a streaming chat with each user's agent in your app.

### [Tools](/docs/camelrun/tools)

Attached and served tools, identity, MCP and OpenAPI sources.

### [Production checklist](/docs/camelrun/production)

What to check before you ship.
