# Run your first agent (/en/docs/developers/agent/quickstart)



Create a service-agent conversation through the API and verify its reply from the event history. This example runs on a server. It needs no web component or embed policy.

Prepare Bun or Node.js with native `fetch`, a Develop organization API Key, access to `deepseek-flash`, and available balance. Set `PRAXIS_API_KEY` in the server process; see [API keys](/en/docs/developers/account/api-keys). If your Key belongs to another environment, align the API address, organization, and model before running. Do not mix environments.

## 1. Create an agent [#create-agent]

Create `first-agent.mjs` with the code below. Append the next three JavaScript blocks to the same file, in order; do not run them separately. The assistant answers a simple arithmetic question so you can recognize a completed first call. Add business tools after connecting successfully.

`POST /agent/v1/agents` creates a configuration from its name, kind, model, and instructions. `tools: []` declares no additional tools. The request helper checks each response and applies a timeout. Failures print only the status and request identifier, never the Key.

```js
import assert from "node:assert/strict";
import { setTimeout as delay } from "node:timers/promises";

const { PRAXIS_API_KEY } = process.env;
assert(PRAXIS_API_KEY, "Set PRAXIS_API_KEY in the server process");
const base = "https://api-develop.deepraxis.com/agent/v1";
async function request(path, body) {
  const response = await fetch(`${base}${path}`, {
    method: body === undefined ? "GET" : "POST",
    headers: {
      Authorization: `Bearer ${PRAXIS_API_KEY}`,
      "Content-Type": "application/json",
    },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
    signal: AbortSignal.timeout(30_000),
  });
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}; request=${response.headers.get("X-Request-Id") ?? response.headers.get("request-id")}`);
  }
  return response.json();
}
const agent = await request("/agents", {
  name: "First support agent", kind: "service", model: "deepseek-flash",
  system: "Answer the user's question briefly. Do not use tools for this exercise.",
  tools: [],
});
assert(agent.id.startsWith("agent_"));
console.log({ agent: agent.id, version: agent.version });
```

**Expected result:** Creation returns HTTP 200 with a public `id` and a `version`. The script prints both and reuses them in later requests. Do not construct IDs yourself.

## 2. Open a session [#create-session]

Append this code. `POST /agent/v1/sessions` pins the version you just created. Omitting `environment_id` selects a light machine; you do not need to create a sandbox first. This example explicitly sets a budget of 100 US cents. That ceiling is not an estimate of the cost of this question.

```js
const session = await request("/sessions", {
  agent: { type: "agent", id: agent.id, version: agent.version },
  title: "First API conversation",
  budget: { type: "limit", max_list_cost: { amount: "100", currency: "USD" } },
});
assert(session.id);
console.log({ session: session.id });
```

**Expected result:** HTTP 201 with the session `id`. No user question has been sent yet, so an `idle` status cannot prove that a reply exists. The budget controls whether execution may continue; it does not guarantee a particular answer.

## 3. Send the first message [#send-message]

Append this code to call `POST /agent/v1/sessions/{session_id}/events`. The `events` array contains one `user.message` with exactly one nonempty text block.

```js
const accepted = await request(`/sessions/${session.id}/events`, {
  events: [{ type: "user.message", content: [{ type: "text", text: "What is 2 + 2?" }] }],
});
assert(accepted.data.some(event => event.type === "user.message"));
```

**Expected result:** HTTP 200 with the accepted user event in `data`. This acknowledges the input; it is not the agent's answer. Do not resend the message just because the answer has not appeared yet.

## 4. Read and verify the reply [#verify]

Append this code, then run `node first-agent.mjs` or `bun first-agent.mjs`. It reads every page from `GET /agent/v1/sessions/{session_id}/events`, finds an `agent.message`, and then finds a subsequent `session.status_idle`. It does not mistake the initial idle state for completion.

```js
let completed = false;
const deadline = Date.now() + 120_000;
while (Date.now() < deadline) {
  const events = [];
  let page;
  do {
    const query = new URLSearchParams({ order: "asc", limit: "100" });
    if (page) query.set("page", page);
    const result = await request(`/sessions/${session.id}/events?${query}`);
    events.push(...result.data);
    page = result.next_page;
  } while (page && Date.now() < deadline);
  const reply = events.findIndex(event => event.type === "agent.message");
  const idle = events.findIndex((event, i) => i > reply && event.type === "session.status_idle");
  if (reply >= 0 && idle > reply) {
    const text = events.filter(event => event.type === "agent.message")
      .flatMap(event => event.content ?? []).filter(block => block.type === "text")
      .map(block => block.text).join("\n");
    assert(text.trim(), "Expected a nonempty reply");
    console.log({ reply: text, completed: true });
    completed = true;
    break;
  }
  await delay(1500);
}
assert(completed, `No completed reply before timeout; inspect session ${session.id}`);
```

**Completion:** The process exits successfully and prints a nonempty `reply` with `completed: true`. The answer should express 4. The 120-second deadline belongs to this example, not to the service. A nonempty-text assertion alone does not validate business correctness; prepare expected answers for your actual use case before launching.

## Troubleshoot failures [#troubleshoot]

| Symptom                                  | Next action                                                                                                                          |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| HTTP 401                                 | Check that the server Key exists, remains valid, and belongs to this environment                                                     |
| HTTP 400                                 | Check request fields and model availability; see [Limits and errors](/en/docs/developers/agent/limits-and-errors)                    |
| HTTP 404                                 | Use IDs returned by creation and check the organization and pinned version                                                           |
| HTTP 429 or a temporary upstream failure | Back off according to the response; inspect the session before creating resources or sending inputs again                            |
| No completed reply before timeout        | Retain the session ID and inspect events for tool approval, budget, or execution failures; the creation status alone is insufficient |

Running the complete script again creates another agent and session. During troubleshooting, query the IDs already printed first. Once you no longer need the test records, manage them through the [API reference](/en/docs/api/native/agent). A running session cannot be treated as a record ready for deletion.

## Next steps [#next]

[Configure an agent](/en/docs/developers/agent/configure), read [Agents and sessions](/en/docs/developers/agent/concepts), or [add PraxisKit to a web page](/en/docs/developers/agent/embed/quickstart).

Give this to your agent:

```text
Follow /en/docs/developers/agent/quickstart on the server.
Ask me for a Develop organization API Key and confirm deepseek-flash access.
Complete when a nonempty agent.message is followed by session.status_idle.
Keep the Key out of browsers and logs. Record the agent ID, version, and session ID.
```
