---
title: First Integration
description: Run an agent through Caracal and call a tool/provider through the Gateway.
pageType: workflow
---

This page walks through integrating an agent with Caracal using the TypeScript, Python, or Go SDK. By the end you will have an agent run that calls a tool/provider resource through the Gateway and leaves both authorization and action-result audit behind.

The instructions assume you have a running stack and a `caracal.toml` from the [Quickstart](./quickstart/).

## How the SDK works

The SDK wraps token exchange and agent lifecycle behind three primitives:

- **`spawn()`** opens an agent run, gets a short-lived Caracal access token, and runs your code inside that identity context.
- **`headers()`** returns the HTTP headers that propagate the agent's identity to downstream services (Authorization, traceparent, baggage).
- **`transport()`** wraps `fetch` (TS/Python) or `*http.Client` (Go) to inject those headers on Gateway-bound outbound requests.

When `spawn()` exits — normally or on error — the agent run is terminated in the Coordinator and active authority for that run is revoked.

## Configuration

All three SDKs read the same base configuration from environment variables:

| Variable | Source | Description |
| -------- | ------ | ----------- |
| `CARACAL_COORDINATOR_URL` | Stack | URL of the Coordinator service |
| `CARACAL_ZONE_ID` | `caracal.toml` | Zone the application belongs to |
| `CARACAL_APPLICATION_ID` | `caracal.toml` | Application ID in that zone |
| `CARACAL_SUBJECT_TOKEN` | Obtained at runtime | Session mandate for this agent run when using static-token startup |

The session mandate is not a static secret. It is short-lived Caracal authority obtained by exchanging the application's client credentials with the STS. The SDK's `fromEnv()` factory handles this transparently when used with `caracal run`. For long-running services, TypeScript, Python, and Go can also read `CARACAL_APP_CLIENT_SECRET` plus `CARACAL_STS_URL` and refresh application authority on demand.

Two additional optional variables:

| Variable | Description |
| -------- | ----------- |
| `CARACAL_GATEWAY_URL` | URL of the Gateway (needed when routing tool calls through it) |
| `CARACAL_RESOURCES` | Comma-separated `resourceId=https://upstream/prefix` pairs |
| `CARACAL_APP_CLIENT_SECRET` | Application client secret for SDK-managed token refresh |
| `CARACAL_STS_URL` | STS base URL required with `CARACAL_APP_CLIENT_SECRET` |
| `CARACAL_APP_RESOURCES` | Comma-separated resource IDs for token exchange when `CARACAL_RESOURCES` is not set |

## Using `caracal run` for local development

The easiest way to get valid env vars into a process is `caracal run`. It reads `caracal.toml`, exchanges credentials with the STS, and injects the resulting tokens as environment variables before launching your process:

```sh
caracal run -- node my-agent.js
caracal run -- python agent.py
caracal run -- ./my-go-agent
```

Inside the launched process, `CARACAL_SUBJECT_TOKEN` is set to the session mandate and the resource mandates declared in `caracal.toml` are set under their configured env var names (e.g. `RESOURCE_TOKEN`).

## TypeScript

Install the SDK:

```sh
npm install @caracalai/sdk
```

Minimal example — spawn an agent run and call a resource through the Gateway:

```typescript
import { Caracal, AgentKind } from "@caracalai/sdk";

const caracal = Caracal.fromEnv();

await caracal.spawn(async () => {
  const ctx = caracal.current();
  console.log("Agent session:", ctx?.agentSessionId);

  // All calls through transport() carry the Caracal access headers.
  const client = caracal.transport();
  const response = await client("https://gateway.example.com/my-resource/endpoint", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ action: "summarize" }),
  });

  const result = await response.json();
  console.log(result);
}, { kind: AgentKind.Ephemeral });
```

`Caracal.fromEnv()` requires the base Coordinator, zone, and application variables plus either `CARACAL_SUBJECT_TOKEN` or `CARACAL_APP_CLIENT_SECRET` with `CARACAL_STS_URL`. Wrap local process startup in `caracal run` to inject a short-lived session mandate automatically.

### Express middleware

To bind inbound Caracal context in an Express app, use the SDK middleware:

```sh
npm install @caracalai/sdk
```

```typescript
import express from "express";
import { Caracal } from "@caracalai/sdk";

const app = express();
const caracal = Caracal.fromEnv();

app.use(caracal.middleware());

app.post("/tool", (req, res) => {
  const ctx = caracal.current();
  // ctx.zoneId, ctx.agentSessionId, ctx.scope are available here
  res.json({ ok: true });
});
```

The middleware extracts the agent's Caracal token from the `Authorization` header and W3C baggage, then binds the `CaracalContext` to the request. Use this connector-verified path only for services you own and can operate with the same revocation and audit contract as the Gateway.

## Python

Install the SDK:

```sh
pip install caracalai-sdk
```

Minimal example using `async with` spawn:

```python
import asyncio
from caracalai_sdk import Caracal, AgentKind

caracal = Caracal.from_env()

async def main():
    async with caracal.spawn(kind=AgentKind.EPHEMERAL) as ctx:
        print("Agent session:", ctx.agent_session_id)

        # transport() returns an httpx.AsyncClient pre-configured with Caracal access headers.
        async with caracal.transport() as client:
            response = await client.post(
                "https://gateway.example.com/my-resource/endpoint",
                json={"action": "summarize"},
            )
            print(response.json())

asyncio.run(main())
```

### FastAPI ASGI middleware

To protect a FastAPI or Starlette service:

```sh
pip install caracalai-sdk
```

```python
from fastapi import FastAPI
from caracalai_sdk import Caracal

app = FastAPI()
caracal = Caracal.from_env()

app.add_middleware(caracal.middleware().__class__, caracal=caracal)

@app.post("/tool")
async def tool():
    ctx = caracal.current()
    # ctx.zone_id, ctx.agent_session_id, ctx.scope are available
    return {"ok": True}
```

The ASGI middleware extracts the agent's Caracal token from inbound request headers and binds the `CaracalContext` to the request scope.

## Go

Install the SDK:

```sh
go get github.com/garudex-labs/caracal/packages/sdk/go
```

Minimal example:

```go
package main

import (
    "context"
    "fmt"
    "io"
    "net/http"

    caracal "github.com/garudex-labs/caracal/packages/sdk/go"
)

func main() {
    c, err := caracal.FromEnv()
    if err != nil {
        panic(err)
    }

    err = c.Spawn(context.Background(), func(ctx context.Context) error {
        cc, _ := c.Current(ctx)
        fmt.Println("Agent session:", cc.AgentSessionID)

        // Transport injects Caracal access headers on every request.
        client := c.Transport(nil)
        resp, err := client.Post(
            "https://gateway.example.com/my-resource/endpoint",
            "application/json",
            nil,
        )
        if err != nil {
            return err
        }
        defer resp.Body.Close()
        body, _ := io.ReadAll(resp.Body)
        fmt.Println(string(body))
        return nil
    })
    if err != nil {
        panic(err)
    }
}
```

### net/http middleware

```sh
go get github.com/garudex-labs/caracal/packages/connectors/nethttp/go
```

```go
import (
    caracal "github.com/garudex-labs/caracal/packages/sdk/go"
    "github.com/garudex-labs/caracal/packages/connectors/nethttp/go"
)

c, _ := caracal.FromEnv()

mux := http.NewServeMux()
mux.HandleFunc("/tool", func(w http.ResponseWriter, r *http.Request) {
    ctx, _ := c.Current(r.Context())
    fmt.Fprintln(w, "Agent session:", ctx.AgentSessionID)
})

http.ListenAndServe(":8090", c.Middleware(mux))
```

## Delegation example

An agent can grant narrower delegated permission to another agent. The parent/source creates the permission, the child/target receives it, and only the receiver can use it.

```typescript
// TypeScript example
await caracal.spawn(async () => {
  // Delegate to a child agent session with narrowed scope.
  await caracal.delegate(
    {
      to: childAgentSessionId,
      toApplicationId: "app2",
      scopes: ["resource://example:read"],
      constraints: { ttlSeconds: 300 },
    },
    async () => {
      // Within this block, the delegated context is active.
      const child = caracal.current();
      console.log("Delegation edge:", child?.delegationEdgeId);
    }
  );
});
```

Revoking the delegated permission from the CLI or admin SDK terminates the child agent run and all descendants immediately.

## What happens if the policy denies the exchange

If policy denies access, the STS returns HTTP 403 and the SDK throws an error. No Caracal access token is issued. The denial is recorded in the audit ledger with the determining policies and diagnostics that caused it. Use `caracal audit tail` or `caracal explain` to inspect the decision.

## Prefer Gateway before direct verification

The Gateway path is the product default because it keeps provider secrets out of agent code, centralizes revocation, and records action-result audit. Use SDK middleware or connector packages only when the resource is yours and can enforce signature checks, replay protection, revocation, and result audit itself. Use adapter paths for provider SDKs or protocols the Gateway cannot proxy only when the adapter preserves the same enforcement contract.

## Next step

You now have a working integration. Read [Key Ideas at a Glance](./key-ideas/) for a concise reference of everything the system does, or jump into [Concepts](/concepts/) to understand the model in depth.
