Skip to main content
First steps

Quickstart

Run your first Qoder Cloud Agent in five steps.

Get your first Qoder Cloud Agent running in five steps: obtain a token, pick an environment, create an Agent, create a Session, and exchange messages. The whole flow uses only curl — no SDK required.

Prerequisites

  • A Qoder account
  • A terminal (macOS, Linux, or WSL)
  • curl, plus jq (optional, for formatting JSON)

Windows users

The commands in this guide use bash syntax. Windows users should use one of the following:
  • Git Bash (recommended): included with Git for Windows
  • WSL: install via wsl --install
If using PowerShell, note these differences:
  • Set environment variables: $env:QODER_ACCESS_TOKEN="your-token" (not export)
  • Use real curl: type curl.exe (PowerShell aliases curl to Invoke-WebRequest)
  • Install jq separately: winget install jqlang.jq

Step 1: Obtain an access token

Choose a PAT or SAT based on the calling identity. The remaining steps use the common QODER_ACCESS_TOKEN environment variable. Set the service endpoints first. The SAT exchange and business API requests must use endpoints in the same region:
export QODER_OPENAPI_BASE_URL="https://openapi.qoder.sh"
export QODER_API_BASE_URL="https://api.qoder.com"

Option 1: PAT (personal user)

  1. Sign in to the Qoder console.
  2. Open Settings → Personal Access Tokens.
  3. Click Create Token, then set a name and expiration.
  4. Copy the token and set it as an environment variable:
export QODER_PAT="your-personal-access-token"
export QODER_ACCESS_TOKEN="$QODER_PAT"
The PAT is shown only once at creation. Save it securely right away.

Option 2: SAT (Service Account Token)

  1. Sign in to the Qoder console as an organization administrator.
  2. Create or select a Service Account under organization management.
  3. Create an API Key from the Service Account details page. You do not select scopes when creating the key; specify the required scopes when exchanging it for an SAT.
  4. Copy the SA Key, then exchange it for an SAT:
export QODER_SA_KEY="sa-key"

SAT_RESPONSE=$(curl --silent --show-error --location "$QODER_OPENAPI_BASE_URL/api/v1/serviceToken/exchange" \
  --header "Authorization: Bearer $QODER_SA_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "grant_type": "client_credentials",
    "audience": "qoder",
    "scope": "qca.access",
    "ttl_seconds": 43200
  }')

export QODER_SAT="$(printf '%s' "$SAT_RESPONSE" | jq -r '.access_token')"
export QODER_ACCESS_TOKEN="$QODER_SAT"
Use the SA Key only to exchange for an SAT; do not send it directly to Cloud Agents APIs. An SAT is valid for at most 12 hours. For Forward APIs, follow the separate token flow in Authentication.
If the same server-side integration also calls Forward APIs, use "scope": "qca.access forward.access" in the exchange request. This SAT has administrator access to Forward resources under the account associated with the Service Account and must be used only in a trusted server-side environment. See Authentication.

Step 2: Pick an Environment

List the available environments and capture the ID:
curl -s "$QODER_API_BASE_URL/api/v1/cloud/environments" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN"
Example response:
{
  "data": [
    {
      "id": "env_019e44eb66bb748cabcd1489f6fa4428",
      "type": "environment",
      "name": "default",
      "description": "",
      "config": {
        "type": "cloud",
        "packages": {
          "type": "packages",
          "apt": [],
          "npm": [],
          "pip": []
        }
      },
      "metadata": {},
      "archived_at": null,
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": "2026-01-01T00:00:00Z"
    }
  ],
  "first_id": "env_019e44eb66bb748cabcd1489f6fa4428",
  "last_id": "env_019e44eb66bb748cabcd1489f6fa4428",
  "has_more": false,
  "next_page": null
}
If the response returns "data": [] (empty array), your account has no environments yet. Create one first:
curl -s -X POST "$QODER_API_BASE_URL/api/v1/cloud/environments" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"default"}'
# Extract the environment ID (use jq to avoid error-prone manual copying of long IDs)
ENV_ID=$(curl -s "$QODER_API_BASE_URL/api/v1/cloud/environments" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" | jq -r '.data[0].id')

echo "Environment ID: $ENV_ID"

Step 3: Create an Agent

Define a general-purpose Agent with a shell tool:
AGENT_RESPONSE=$(curl -s -X POST "$QODER_API_BASE_URL/api/v1/cloud/agents" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-first-agent",
    "model": "ultimate",
    "system": "You are an efficient programming assistant skilled at writing code and troubleshooting issues.",
    "tools": [
      {"type": "agent_toolset_20260401", "enabled_tools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch", "WebSearch"]}
    ]
  }')
echo "$AGENT_RESPONSE" | jq .
AGENT_ID=$(echo "$AGENT_RESPONSE" | jq -r '.id')
echo "Agent ID: $AGENT_ID"
Example response:
{
  "id": "agent_019e451902fe7a2ca42c2dfc62d9320e",
  "type": "agent",
  "name": "my-first-agent",
  "description": "",
  "model": "ultimate",
  "system": "You are an efficient programming assistant skilled at writing code and troubleshooting issues.",
  "tools": [{"type": "agent_toolset_20260401", "enabled_tools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch", "WebSearch"]}],
  "mcp_servers": [],
  "skills": [],
  "metadata": {},
  "multiagent": null,
  "version": 1,
  "archived_at": null,
  "created_at": "2026-05-18T10:00:00Z",
  "updated_at": "2026-05-18T10:00:00Z"
}

Step 4: Create a Session

Creating a Session requires two parameters: agent (Agent ID or object) and environment_id (Environment ID). Bind the Agent to the Environment to create a runtime instance:
SESSION_RESPONSE=$(curl -s -X POST "$QODER_API_BASE_URL/api/v1/cloud/sessions" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"agent\": \"$AGENT_ID\",
    \"environment_id\": \"$ENV_ID\"
  }")
echo "$SESSION_RESPONSE" | jq .
SESSION_ID=$(echo "$SESSION_RESPONSE" | jq -r '.id')
echo "Session ID: $SESSION_ID"
Example response:
{
  "id": "sess_019e451b146470cda02c560bf019fb37",
  "type": "session",
  "agent": {
    "id": "agent_019e451902fe7a2ca42c2dfc62d9320e",
    "type": "agent",
    "name": "my-first-agent",
    "model": {"id": "ultimate", "effective_context_window": 200000},
    "version": 1
  },
  "environment_id": "env_019e44eb66bb748cabcd1489f6fa4428",
  "status": "idle",
  "title": null,
  "metadata": {},
  "resources": [],
  "vault_ids": [],
  "deployment_id": null,
  "outcome_evaluations": [],
  "stats": {"active_seconds": 0, "duration_seconds": 0},
  "environment_variables": {},
  "archived_at": null,
  "created_at": "2026-05-18T10:01:00Z",
  "updated_at": "2026-05-18T10:01:00Z"
}
The Session starts in idle status. It will only begin processing once you send a message in the next step.

Step 5: Send a Message and Stream Events

Send a user message to the Session, then receive Agent responses live over SSE:
# Send a message (note: the request body wraps events in an array)
curl -s -X POST "$QODER_API_BASE_URL/api/v1/cloud/sessions/$SESSION_ID/events" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "type": "user.message",
        "content": [{"type": "text", "text": "Write a Python function that computes the Fibonacci sequence and run a test."}]
      }
    ]
  }' | jq .
# Stream events over SSE
curl -s -N "$QODER_API_BASE_URL/api/v1/cloud/sessions/$SESSION_ID/events/stream" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN"
Example event stream output:
id: evt_019ef515680d7a0ebd3160ca45ec5484
event: user.message
data: {"content":[{"text":"Write a Python function that computes the Fibonacci sequence and run a test.","type":"text"}],"id":"evt_019ef515680d7a0ebd3160ca45ec5484","processed_at":"2026-06-23T15:24:41.357659669Z","type":"user.message"}

id: evt_019ef515681a7c52a0b45aa87625f121
event: session.status_running
data: {"id":"evt_019ef515681a7c52a0b45aa87625f121","processed_at":"2026-06-23T15:24:41.357659669Z","type":"session.status_running"}

event: heartbeat
data: {}

id: evt_49dce735a4ab4fc4
event: agent.thinking
data: {"id":"evt_49dce735a4ab4fc4","processed_at":"2026-06-23T15:24:49.357659Z","type":"agent.thinking"}

id: evt_02f80c5a8c245e04
event: agent.message
data: {"content":[{"text":"I'll create a Python module with the Fibonacci function and comprehensive tests.","type":"text"}],"id":"evt_02f80c5a8c245e04","processed_at":"2026-06-23T15:24:49.357659Z","type":"agent.message"}

id: evt_50739b167fb7c6d3
event: agent.tool_use
data: {"evaluated_permission":"allow","id":"evt_50739b167fb7c6d3","input":{"content":"def fibonacci(n): ...","file_path":"/data/fibonacci.py"},"name":"Write","processed_at":"2026-06-23T15:24:49.357659Z","type":"agent.tool_use"}

id: evt_e7c375f3605ff156
event: agent.tool_result
data: {"content":[{"text":"Write file /data/fibonacci.py successfully","type":"text"}],"id":"evt_e7c375f3605ff156","is_error":false,"processed_at":"2026-06-23T15:25:01.853844Z","type":"agent.tool_result"}

id: evt_60eba1483797a419
event: session.status_idle
data: {"id":"evt_60eba1483797a419","processed_at":"2026-06-23T15:25:24.436729Z","stop_reason":{"type":"end_turn"},"type":"session.status_idle"}
  • Every event (except heartbeat) includes an id: line and the JSON payload contains id, type, and processed_at fields.
  • heartbeat events are sent approximately every 15 seconds to keep the connection alive.
  • The content field in agent.message uses the [{"type":"text","text":"..."}] array format.
  • session.status_running / session.status_idle carry no extra fields beyond id, type, processed_at, and (for idle) stop_reason.
  • agent.thinking signals that the model is reasoning but contains no content or text field.

End-to-End Script

The whole flow combined into a single runnable script:
#!/bin/bash
# Qoder Cloud Agents quickstart script
# Usage: export QODER_ACCESS_TOKEN="your-token" && bash quickstart.sh
set -euo pipefail

QODER_API_BASE_URL="${QODER_API_BASE_URL:-https://api.qoder.com}"
BASE_URL="$QODER_API_BASE_URL/api/v1/cloud"
HEADERS=(
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN"
)

echo "=== Step 1: Fetch environment ==="
ENV_ID=$(curl -s "$BASE_URL/environments" "${HEADERS[@]}" | jq -r '.data[0].id')
if [ "$ENV_ID" = "null" ] || [ -z "$ENV_ID" ]; then
  echo "No environment found. Creating a default one..."
  ENV_ID=$(curl -s -X POST "$BASE_URL/environments" \
    "${HEADERS[@]}" \
    -H "Content-Type: application/json" \
    -d '{"name":"default"}' | jq -r '.id')
fi
echo "Environment ID: $ENV_ID"

echo "=== Step 2: Get or create the Agent ==="
AGENT_ID=$(curl -s "$BASE_URL/agents" "${HEADERS[@]}" | jq -r '.data[0].id')
if [ "$AGENT_ID" = "null" ] || [ -z "$AGENT_ID" ]; then
  AGENT_ID=$(curl -s -X POST "$BASE_URL/agents" \
    "${HEADERS[@]}" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "quickstart-agent",
      "model": "ultimate",
      "system": "You are an efficient programming assistant.",
      "tools": [{"type": "agent_toolset_20260401", "enabled_tools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch", "WebSearch"]}]
    }' | jq -r '.id')
fi
echo "Agent ID: $AGENT_ID"

echo "=== Step 3: Create the Session ==="
SESSION_ID=$(curl -s -X POST "$BASE_URL/sessions" \
  "${HEADERS[@]}" \
  -H "Content-Type: application/json" \
  -d "{\"agent\": \"$AGENT_ID\", \"environment_id\": \"$ENV_ID\"}" | jq -r '.id')
echo "Session ID: $SESSION_ID"

echo "=== Step 4: Send a message ==="
curl -s -X POST "$BASE_URL/sessions/$SESSION_ID/events" \
  "${HEADERS[@]}" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {"type": "user.message", "content": [{"type": "text", "text": "Print Hello World and tell me the current system time."}]}
    ]
  }' | jq .

echo "=== Step 5: Stream events ==="
echo "(Press Ctrl+C to exit)"
curl -s -N "$BASE_URL/sessions/$SESSION_ID/events/stream" "${HEADERS[@]}"

FAQ

Q: I'm getting 401 Unauthorized. A: Check that $QODER_ACCESS_TOKEN is set correctly and has not expired. PAT users should create a replacement token; Service Account users should exchange the SA Key for a new SAT. Q: Creating an Agent returns 400 Bad Request. A: Verify the request JSON. The model field must be a valid value (such as "ultimate"), and tools must be an array. Q: The Session stays in idle and emits no events. A: A newly created Session starts in idle status. You must send a user.message event (Step 5) to trigger Agent execution. Q: My SSE stream disconnected. A: The stream endpoint supports the Last-Event-ID header for reconnection replay. Pass the last event id you received; the stream resumes from the event after that ID. Event-type query filters are not currently supported. Q: GET /api/v1/cloud/environments returns an empty array. A: New accounts may not have a pre-provisioned environment. Follow the tip in Step 2 to create one manually.

Next steps