> ## Documentation Index
> Fetch the complete documentation index at: https://docs.checkfu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Follow the public Agent and Session API flow when your Workspace has qualified execution.

Create an Agent, start a Session, and send it a task. Then follow the event stream to read its answer and confirm that the task finished.

This example selects Pi with Gemini 2.5 Flash and a Workspace credential you provide.
It is one explicit harness/model choice, not a default. For other combinations, see
[Harnesses and models](/concepts/harnesses-and-models).

<Warning>
  Plain HTTP against the Checkfu API needs no private package.
  The Checkfu CLI and TypeScript SDK are optional private-alpha artifacts; the HTTP requests work without them.
  Live readiness is deployment- and Workspace-specific and is not asserted by this page.
  Confirm [capability status](/getting-started/status) before making availability part of your application's contract.
</Warning>

## Before you start

* A Workspace API key with the `admin` role. This walkthrough requires it for the Environment permission grant in step 2. See [Get access](/reference/access).
* A Gemini API-key credential in the same Workspace. Add it in [Model credentials](https://app.checkfu.com/model-credentials) with provider `google-ai-studio`, then copy its `mcred_…` ID. The example pins that credential; it does not assume Checkfu-managed model supply or fall back to it if the key is refused.
* Workspace execution configured to run Pi with this model. Check the Workspace's [capability status](/getting-started/status).
* For the shell examples, Bash, `curl`, `jq`, and `openssl`.

Replace the API key and credential ID below with your own values. The credential ID is a reference, not the provider's secret key.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
export CHECKFU_API_KEY="your_api_key"
export CHECKFU_MODEL_CREDENTIAL_ID="mcred_0123456789abcdef0123456789abcdef"
CHECKFU_QUICKSTART_ID="$(openssl rand -hex 16)" && export CHECKFU_QUICKSTART_ID
```

Keep `CHECKFU_QUICKSTART_ID` and each request body unchanged when retrying. Stop if a command fails or does not return an ID; do not run later steps with an empty or old ID. Use this new Session for one task at a time.

cURL needs no Checkfu package. The other tabs assume you already installed the CLI or SDK using the instructions supplied with your authorized artifact. See [CLI access](/getting-started/cli-access) or the [TypeScript SDK reference](/reference/typescript-sdk).

For TypeScript, reuse this client and the same request identity:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Checkfu, agentEnvironmentUseAssignment } from "@checkfu/sdk"

const apiKey = process.env.CHECKFU_API_KEY
const modelCredentialId = process.env.CHECKFU_MODEL_CREDENTIAL_ID
const quickstartId = process.env.CHECKFU_QUICKSTART_ID
if (!apiKey || !modelCredentialId || !quickstartId) {
  throw new Error("Set the three quickstart environment variables before continuing.")
}
const checkfu = new Checkfu({ apiKey })
```

## 1. Create an Agent

The Agent selects Pi, Gemini 2.5 Flash, and your credential. Creating it also creates immutable Version 1.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  checkfu_create_agent() {
    set -o pipefail
    CHECKFU_AGENT_ID="$(
    curl --fail-with-body --request POST \
      "https://api.checkfu.com/v1/agents" \
      --header "Authorization: Bearer $CHECKFU_API_KEY" \
      --header "Checkfu-Version: 2026-08-31" \
      --header "Idempotency-Key: quickstart-agent-$CHECKFU_QUICKSTART_ID" \
      --header "Content-Type: application/json" \
      --data '{
        "name": "quickstart",
        "model": {
          "id": "gemini-2.5-flash",
          "provider": "google-ai-studio",
          "model_credential_id": "'"$CHECKFU_MODEL_CREDENTIAL_ID"'"
        },
        "harness": "pi",
        "system": "You are a helpful assistant."
      }' | jq -er '.id // error("Checkfu did not return an id; check the request error above")'
  )" || return 1
    export CHECKFU_AGENT_ID
  }
  checkfu_create_agent
  ```

  ```sh CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  checkfu_create_agent() {
    set -o pipefail
    CHECKFU_AGENT_ID="$(
    checkfu agent create \
      --input '{
    "name": "quickstart",
    "model": {
      "id": "gemini-2.5-flash",
      "provider": "google-ai-studio",
      "model_credential_id": "'"$CHECKFU_MODEL_CREDENTIAL_ID"'"
    },
    "harness": "pi",
    "system": "You are a helpful assistant."
  }' \
      --json \
      --idempotency-key "quickstart-agent-$CHECKFU_QUICKSTART_ID" | jq -er '.id // error("Checkfu did not return an id; check the request error above")'
  )" || return 1
    export CHECKFU_AGENT_ID
  }
  checkfu_create_agent
  ```

  ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const agent = await checkfu.agents.create({
    name: "quickstart",
    model: {
      id: "gemini-2.5-flash",
      provider: "google-ai-studio",
      model_credential_id: modelCredentialId,
    },
    harness: "pi",
    system: "You are a helpful assistant.",
  }, { idempotencyKey: `quickstart-agent-${quickstartId}` })
  ```
</CodeGroup>

The shell tabs set `CHECKFU_AGENT_ID`; TypeScript keeps `agent.id`. See [Agents](/concepts/agents) for configuration and versioning.

## 2. Start a Session

Create an Environment and grant the Agent permission to use it. Then start a Session with that Agent and Environment. The Session records their selected configuration.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  checkfu_create_session() {
    set -o pipefail
    CHECKFU_ENVIRONMENT_ID="$(
    curl --fail-with-body --request POST \
      "https://api.checkfu.com/v1/environments" \
      --header "Authorization: Bearer $CHECKFU_API_KEY" \
      --header "Checkfu-Version: 2026-08-31" \
      --header "Idempotency-Key: quickstart-environment-$CHECKFU_QUICKSTART_ID" \
      --header "Content-Type: application/json" \
      --data '{
        "name": "quickstart"
      }' | jq -er '.id // error("Checkfu did not return an id; check the request error above")'
  )" || return 1
    export CHECKFU_ENVIRONMENT_ID
    # The Agent may only run where it is granted.
  curl --fail-with-body --request POST \
    "https://api.checkfu.com/v1/permission-assignments" \
    --header "Authorization: Bearer $CHECKFU_API_KEY" \
    --header "Checkfu-Version: 2026-08-31" \
    --header "Idempotency-Key: quickstart-environment-access-$CHECKFU_QUICKSTART_ID" \
    --header "Content-Type: application/json" \
    --data '{
      "subject": {
        "kind": "agent_definition",
        "agent": "'"$CHECKFU_AGENT_ID"'"
      },
      "resource": {
        "kind": "environment",
        "id": "'"$CHECKFU_ENVIRONMENT_ID"'"
      },
      "permission": "use_environment"
    }' || return 1
    CHECKFU_SESSION_ID="$(
    curl --fail-with-body --request POST \
      "https://api.checkfu.com/v1/sessions" \
      --header "Authorization: Bearer $CHECKFU_API_KEY" \
      --header "Checkfu-Version: 2026-08-31" \
      --header "Idempotency-Key: quickstart-create-$CHECKFU_QUICKSTART_ID" \
      --header "Content-Type: application/json" \
      --data '{
        "agent": {
          "type": "agent",
          "id": "'"$CHECKFU_AGENT_ID"'"
        },
        "environment_id": "'"$CHECKFU_ENVIRONMENT_ID"'"
      }' | jq -er '.id // error("Checkfu did not return an id; check the request error above")'
  )" || return 1
    export CHECKFU_SESSION_ID
  }
  checkfu_create_session
  ```

  ```sh CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  checkfu_create_session() {
    set -o pipefail
    CHECKFU_ENVIRONMENT_ID="$(
    checkfu operation invoke environments.createEnvironment \
      --body '{"name":"quickstart"}' \
      --idempotency-key "quickstart-environment-$CHECKFU_QUICKSTART_ID" | jq -er '.id // error("Checkfu did not return an id; check the request error above")'
  )" || return 1
    export CHECKFU_ENVIRONMENT_ID
    # The Agent may only run where it is granted.
  checkfu operation invoke permissionAssignments.createPermissionAssignment \
    --body '{
    "subject": {
      "kind": "agent_definition",
      "agent": "'"$CHECKFU_AGENT_ID"'"
    },
    "resource": {
      "kind": "environment",
      "id": "'"$CHECKFU_ENVIRONMENT_ID"'"
    },
    "permission": "use_environment"
  }' \
    --idempotency-key "quickstart-environment-access-$CHECKFU_QUICKSTART_ID" > /dev/null || return 1
    CHECKFU_SESSION_ID="$(
    checkfu session create \
      --agent "$CHECKFU_AGENT_ID" \
      --environment "$CHECKFU_ENVIRONMENT_ID" \
      --idempotency-key "quickstart-create-$CHECKFU_QUICKSTART_ID"
  )" || return 1
    export CHECKFU_SESSION_ID
  }
  checkfu_create_session
  ```

  ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const environment = await checkfu.environments.create({
    name: "quickstart",
  }, { idempotencyKey: `quickstart-environment-${quickstartId}` })
  // The Agent may only run where it is granted.
  await checkfu.permissionAssignments.create(
    agentEnvironmentUseAssignment(agent.id, environment.id),
    { idempotencyKey: `quickstart-environment-access-${quickstartId}` },
  )
  const session = await checkfu.sessions.create({
    agent: { type: "agent", id: agent.id },
    environment_id: environment.id,
  }, { idempotencyKey: `quickstart-create-${quickstartId}` })
  ```
</CodeGroup>

The shell tabs set `CHECKFU_SESSION_ID`; TypeScript keeps `session.id`. A refused request must be resolved before sending a task; Checkfu does not switch harnesses or model credentials for you.

<Accordion title="Selected response fields">
  ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "id": "sess_0123456789abcdef0123456789abcdef",
    "type": "session",
    "status": "idle",
    "agent": {
      "type": "agent",
      "id": "agent_0123456789abcdef0123456789abcdef",
      "version": 1,
      "name": "quickstart",
      "harness": "pi",
      "model": {
        "id": "gemini-2.5-flash",
        "provider": "google-ai-studio",
        "model_credential_id": "mcred_0123456789abcdef0123456789abcdef"
      },
      "system": "You are a helpful assistant."
    },
    "environment_id": "env_0123456789abcdef0123456789abcdef",
    "title": null,
    "created_at": "2026-07-16T18:00:00.000Z",
    "updated_at": "2026-07-16T18:00:00.000Z"
  }
  ```

  The embedded `agent.version` and `environment_id` identify the immutable AgentVersion and Environment authority frozen at admission.
</Accordion>

## 3. Send a task

Send one message and keep the returned event's `seq`. This receipt identifies the accepted input. Authenticated server context supplies durable actor attribution.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  checkfu_send_message() {
    set -o pipefail
    CHECKFU_INPUT_SEQ="$(
    curl --fail-with-body --request POST \
      "https://api.checkfu.com/v1/sessions/$CHECKFU_SESSION_ID/events" \
      --header "Authorization: Bearer $CHECKFU_API_KEY" \
      --header "Checkfu-Version: 2026-08-31" \
      --header "Idempotency-Key: quickstart-message-$CHECKFU_QUICKSTART_ID" \
      --header "Content-Type: application/json" \
      --data '{
        "type": "user.message",
        "payload": {
          "content": "Hello from the Checkfu quickstart.",
          "caused_by": {
            "kind": "api"
          }
        }
      }' | jq -er '.seq // error("Checkfu did not return the stored event; check the request error above")'
  )" || return 1
    export CHECKFU_INPUT_SEQ
  }
  checkfu_send_message
  ```

  ```sh CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  checkfu_send_message() {
    set -o pipefail
    CHECKFU_INPUT_SEQ="$(
      checkfu session steer "$CHECKFU_SESSION_ID" \
        "Hello from the Checkfu quickstart." \
        --idempotency-key "quickstart-message-$CHECKFU_QUICKSTART_ID" \
        --json | jq -er '.seq // error("Checkfu did not return the stored event")'
    )" || return 1
    export CHECKFU_INPUT_SEQ
  }
  checkfu_send_message
  ```

  ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const inputEvent = await checkfu.sessions.events.send(session.id, {
    type: "user.message",
    payload: {
      content: "Hello from the Checkfu quickstart.",
      caused_by: { kind: "api" },
    },
  }, { idempotencyKey: `quickstart-message-${quickstartId}` })
  ```
</CodeGroup>

The shell tabs set `CHECKFU_INPUT_SEQ`; TypeScript keeps `inputEvent`. Retrying this request with the same key and body recovers the same accepted event. Sending a new key would submit another task.

## 4. Read the answer

Follow from just after the accepted message. The TypeScript example prints the Agent's answer and only reports success after a normal end-of-turn event. Keep this Session free of other inputs until this task finishes.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --fail-with-body --no-buffer \
    "https://api.checkfu.com/v1/sessions/$CHECKFU_SESSION_ID/events/stream" \
    --header "Authorization: Bearer $CHECKFU_API_KEY" \
    --header "Checkfu-Version: 2026-08-31" \
    --header "Last-Event-ID: $CHECKFU_INPUT_SEQ"
  ```

  ```sh CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  checkfu session follow "$CHECKFU_SESSION_ID" --from "$CHECKFU_INPUT_SEQ" --until-boundary &&
    checkfu operation invoke sessions.listEvents \
      --path '{"id":"'"$CHECKFU_SESSION_ID"'"}' \
      --query '{"after":"'"$CHECKFU_INPUT_SEQ"'"}'
  ```

  ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  let receivedAnswer = false
  let withheldAnswer = false
  let succeeded = false

  for await (const event of checkfu.sessions.events.stream(session.id, {
    lastEventId: inputEvent.seq,
  })) {
    const payload = event.payload
    if (typeof payload !== "object" || payload === null) continue
    if (event.type === "session.status_waiting") {
      console.log("Session is waiting. Inspect the pending request before continuing.")
    }
    if (event.type === "agent.message" && !("subagent" in payload)) {
      if ("content" in payload && typeof payload.content === "string" && payload.content.trim()) {
        console.log(payload.content)
        receivedAnswer = true
      } else if ("withheld" in payload) {
        withheldAnswer = true
        console.log("An answer was withheld; inspect the Session before continuing.")
      }
    }
    if (event.type === "session.status_failed") {
      throw new Error(`Session failed: ${"reason" in payload ? payload.reason : "unknown reason"}`)
    }
    if (event.type === "session.status_canceled") {
      throw new Error("Session canceled")
    }
    if (event.type === "session.status_idle" || event.type === "session.status_completed") {
      const stopReason = "stop_reason" in payload ? payload.stop_reason : undefined
      if (stopReason === "requires_action") {
        console.log("Action required. Handle the pending request; this is not success.")
        continue
      }
      if (stopReason !== "end_turn") {
        throw new Error(`Turn stopped: ${stopReason}`)
      }
      if (!receivedAnswer || withheldAnswer) {
        throw new Error("The turn ended without a complete visible answer.")
      }
      succeeded = true
      console.log("First task completed.")
      break
    }
  }
  if (!succeeded) {
    throw new Error("Stream ended before success. Resume this Session; do not resend the task.")
  }
  ```
</CodeGroup>

Every tab starts after the accepted message's `seq`, so earlier history in the Session cannot end the follow.

A visible primary `agent.message` followed by `session.status_idle` or `session.status_completed` with `stop_reason: "end_turn"` marks success for this task. `requires_action` needs a response before work can continue; failure, cancellation, withheld output, other stop reasons and stream disconnection are not success. The CLI returns at a boundary, including a wait, then prints saved events as JSON so you can inspect `payload.stop_reason`. cURL stays connected.

If the CLI JSON has a non-null `next_page`, repeat `sessions.listEvents` with the same `--path` and `--query '{"page":"<next_page>"}'`, replacing the placeholder with that token and omitting `after`. Continue until you reach the boundary after the answer; a partial page or `[idle]` alone does not establish success.

With cURL, read the `data:` payloads and press <kbd>Ctrl</kbd>+<kbd>C</kbd> after you have checked the answer and boundary. A CLI exit or closed stream alone does not prove success.

The SDK preserves the answer flags during automatic reconnects. After a process restart, replay from the original `inputEvent.seq` unless you restored both answer flags with your cursor.

If work requires action, [handle the pending approval or tool response](/guides/handle-an-approval) and continue reading the same Session. If the connection breaks, [resume the stream](/guides/resume-a-stream) from the last event you processed; do not create another Session or resend the message to reconnect.

<Accordion title="Read saved events">
  Use the paged event log to inspect persisted history. Follow its pagination to reach the events after your accepted input.

  <CodeGroup>
    ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl --fail-with-body --request GET \
      "https://api.checkfu.com/v1/sessions/$CHECKFU_SESSION_ID/events?limit=2" \
      --header "Authorization: Bearer $CHECKFU_API_KEY" \
      --header "Checkfu-Version: 2026-08-31"
    ```

    ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
    const events = await checkfu.sessions.events.list(session.id, {
      limit: 2,
    })
    ```
  </CodeGroup>
</Accordion>

## If a request fails

* `401 auth.invalid_key`: check the API key.
* `400 validation.malformed`: check the request body and `Checkfu-Version`.
* `404 validation.not_found`: check that the resource belongs to the key's Workspace.

Read the stable `error.type` in the [error reference](/reference/errors). Correct the cause before retrying; do not treat every conflict as a stale Agent or switch credentials automatically.

## Continue building

[Add an agent to your app](/guides/add-an-agent-to-your-app) to send more tasks and show events in your UI. Add [tools](/concepts/tools) or [context](/concepts/context) as the work needs them.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.