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

# Authentication and API keys

> Titan supports JWT-based access for interactive sessions and API key access for backend automation.

Titan supports two primary access patterns:

* JWT-based access for signed-in users and frontend applications
* API key access for backend systems and automation

Both are supplied as bearer tokens, but they are created and managed differently.

Examples below use **`AUTH_URL`**, **`JWT_TOKEN`**, and **`TITAN_TOKEN`** where relevant. Tab labels match other integration pages so your language choice stays in sync.

## When to use each token type

| Token type | Best for                                    | Typical caller                                |
| ---------- | ------------------------------------------- | --------------------------------------------- |
| JWT        | Interactive sessions and browser-based apps | Dashboard, frontend clients                   |
| API key    | Stable server-to-server automation          | Backend jobs, internal services, CI workflows |

## Before you begin

| Requirement          | Why it matters                                         |
| -------------------- | ------------------------------------------------------ |
| Auth service URL     | To sign in, fetch sessions, and manage API keys        |
| A Titan user account | To obtain a JWT and create user API keys               |
| Required scopes      | To call the Task Service and related APIs successfully |

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    export AUTH_URL="https://api.webscraping.titannet.io"
    export TASK_SERVICE_URL="https://api.webscraping.titannet.io"
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // export AUTH_URL / TASK_SERVICE_URL in your shell, or set in code:
    // os.Setenv("AUTH_URL", "https://api.webscraping.titannet.io")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    process.env.AUTH_URL ??= "https://api.webscraping.titannet.io";
    process.env.TASK_SERVICE_URL ??= "https://api.webscraping.titannet.io";
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os

    os.environ.setdefault("AUTH_URL", "https://api.webscraping.titannet.io")
    os.environ.setdefault("TASK_SERVICE_URL", "https://api.webscraping.titannet.io")
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    // std::env::set_var("AUTH_URL", "https://api.webscraping.titannet.io");
    ```
  </Tab>
</Tabs>

## How authentication fits into the platform

```mermaid theme={null}
flowchart LR
    User -->|Sign in| Auth[Auth Service]
    Auth -->|JWT| User
    User -->|JWT or API key| TaskService[Task Service]
    User -->|JWT or API key| Analytics[Analytics Service]
    User -->|JWT or API key| Billing[Billing Service]
```

## Use a JWT

JWTs are best when a user is signed in through the dashboard or when a frontend needs to call Titan APIs on behalf of a user.

### Step 1: sign in

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS -X POST "$AUTH_URL/api/v1/auth/login" \
      -H "Content-Type: application/json" \
      -d '{ "email": "you@example.com", "password": "your-password" }'
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
      "bytes"
      "encoding/json"
      "fmt"
      "io"
      "net/http"
      "os"
    )

    func main() {
      authURL := os.Getenv("AUTH_URL")
      body, _ := json.Marshal(map[string]string{
        "email":    "you@example.com",
        "password": "your-password",
      })
      req, _ := http.NewRequest("POST", authURL+"/api/v1/auth/login", bytes.NewReader(body))
      req.Header.Set("Content-Type", "application/json")
      resp, err := http.DefaultClient.Do(req)
      if err != nil {
        panic(err)
      }
      defer resp.Body.Close()
      out, _ := io.ReadAll(resp.Body)
      fmt.Println(string(out))
    }
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const authUrl = process.env.AUTH_URL!;
    const res = await fetch(`${authUrl}/api/v1/auth/login`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        email: "you@example.com",
        password: "your-password",
      }),
    });
    console.log(await res.text());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import json
    import os
    import urllib.request

    auth_url = os.environ["AUTH_URL"]
    req = urllib.request.Request(
        f"{auth_url}/api/v1/auth/login",
        data=json.dumps(
            {"email": "you@example.com", "password": "your-password"}
        ).encode(),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        print(resp.read().decode())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use serde_json::json;

    let auth_url = std::env::var("AUTH_URL").expect("AUTH_URL");
    let body = json!({
        "email": "you@example.com",
        "password": "your-password",
    });
    let resp = ureq::post(format!("{auth_url}/api/v1/auth/login"))
        .set("Content-Type", "application/json")
        .send_json(body)
        .expect("login");
    println!("{}", resp.into_string().expect("body"));
    ```
  </Tab>
</Tabs>

### Step 2: retrieve the active session

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS "$AUTH_URL/api/v1/auth/session" \
      -H "Authorization: Bearer $JWT_TOKEN"
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    req, _ := http.NewRequest("GET", os.Getenv("AUTH_URL")+"/api/v1/auth/session", nil)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("JWT_TOKEN"))
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
      panic(err)
    }
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    fmt.Println(string(out))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const authUrl = process.env.AUTH_URL!;
    const jwt = process.env.JWT_TOKEN!;
    const res = await fetch(`${authUrl}/api/v1/auth/session`, {
      headers: { Authorization: `Bearer ${jwt}` },
    });
    console.log(await res.text());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import urllib.request

    req = urllib.request.Request(
        f"{os.environ['AUTH_URL']}/api/v1/auth/session",
        headers={"Authorization": f"Bearer {os.environ['JWT_TOKEN']}"},
        method="GET",
    )
    with urllib.request.urlopen(req) as resp:
        print(resp.read().decode())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let auth_url = std::env::var("AUTH_URL").expect("AUTH_URL");
    let jwt = std::env::var("JWT_TOKEN").expect("JWT_TOKEN");
    let resp = ureq::get(format!("{auth_url}/api/v1/auth/session"))
        .set("Authorization", &format!("Bearer {jwt}"))
        .call()
        .expect("session");
    println!("{}", resp.into_string().expect("body"));
    ```
  </Tab>
</Tabs>

Use the returned token as your bearer token in Task Service requests.

## Create an API key

API keys are the recommended choice for backend integrations.

### Step 1: obtain a JWT

You create API keys as an authenticated user, so start with a JWT.

### Step 2: create a scoped key

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -sS -X POST "$AUTH_URL/api/v1/api-keys" \
      -H "Authorization: Bearer $JWT_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "My backend integration",
        "scopes": ["tasks:read", "tasks:write", "executions:read", "executions:write", "data:read"]
      }'
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    payload := map[string]any{
      "name": "My backend integration",
      "scopes": []string{
        "tasks:read", "tasks:write", "executions:read", "executions:write", "data:read",
      },
    }
    b, _ := json.Marshal(payload)
    req, _ := http.NewRequest("POST", os.Getenv("AUTH_URL")+"/api/v1/api-keys", bytes.NewReader(b))
    req.Header.Set("Authorization", "Bearer "+os.Getenv("JWT_TOKEN"))
    req.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
      panic(err)
    }
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    fmt.Println(string(out))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const authUrl = process.env.AUTH_URL!;
    const jwt = process.env.JWT_TOKEN!;
    const res = await fetch(`${authUrl}/api/v1/api-keys`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${jwt}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name: "My backend integration",
        scopes: [
          "tasks:read",
          "tasks:write",
          "executions:read",
          "executions:write",
          "data:read",
        ],
      }),
    });
    console.log(await res.text());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import json
    import os
    import urllib.request

    payload = {
        "name": "My backend integration",
        "scopes": [
            "tasks:read",
            "tasks:write",
            "executions:read",
            "executions:write",
            "data:read",
        ],
    }
    req = urllib.request.Request(
        f"{os.environ['AUTH_URL']}/api/v1/api-keys",
        data=json.dumps(payload).encode(),
        headers={
            "Authorization": f"Bearer {os.environ['JWT_TOKEN']}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        print(resp.read().decode())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use serde_json::json;

    let auth_url = std::env::var("AUTH_URL").expect("AUTH_URL");
    let jwt = std::env::var("JWT_TOKEN").expect("JWT_TOKEN");
    let body = json!({
        "name": "My backend integration",
        "scopes": [
            "tasks:read", "tasks:write", "executions:read", "executions:write", "data:read"
        ],
    });
    let resp = ureq::post(format!("{auth_url}/api/v1/api-keys"))
        .set("Authorization", &format!("Bearer {jwt}"))
        .set("Content-Type", "application/json")
        .send_json(body)
        .expect("api-keys");
    println!("{}", resp.into_string().expect("body"));
    ```
  </Tab>
</Tabs>

Example response:

```json theme={null}
{
  "id": "key-uuid",
  "name": "My backend integration",
  "key": "titan_sk_live_abc123...",
  "scopes": ["tasks:read", "tasks:write", "executions:read", "executions:write", "data:read"],
  "created_at": "2025-01-01T00:00:00Z"
}
```

Store the `key` value securely. It is the secret your automation will use.

## Common scopes

| Scope              | Use it when you need to...                                                          |
| ------------------ | ----------------------------------------------------------------------------------- |
| `tasks:read`       | List or inspect tasks                                                               |
| `tasks:write`      | Create, update, delete, or run tasks                                                |
| `executions:read`  | Inspect execution state and history                                                 |
| `executions:write` | Trigger and control executions                                                      |
| `data:read`        | Export results, datasets, or media                                                  |
| `analytics:read`   | Read usage or execution analytics                                                   |
| `billing:read`     | Browse billing and wallet information                                               |
| `billing:write`    | Change plan selection and other billing mutations (when exposed by your deployment) |

## Use the token in API requests

All user-facing examples in this documentation use a bearer header on Task Service calls:

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -H "Authorization: Bearer $TITAN_TOKEN" "$TASK_SERVICE_URL/api/v1/tasks"
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const headers = {
      Authorization: `Bearer ${process.env.TITAN_TOKEN}`,
      "Content-Type": "application/json",
    };
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    headers = {
        "Authorization": f"Bearer {os.environ['TITAN_TOKEN']}",
        "Content-Type": "application/json",
    }
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    .set(
        "Authorization",
        &format!("Bearer {}", std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN")),
    )
    ```
  </Tab>
</Tabs>

That token may be either:

* A JWT
* An API key

## Best practices

| Practice                                 | Why it matters                                     |
| ---------------------------------------- | -------------------------------------------------- |
| Use API keys for backend automation      | They are more stable than user sessions            |
| Use the minimum scope set                | Limits blast radius if a key is exposed            |
| Keep JWTs in frontend session flows only | Avoids mixing browser auth with backend automation |
| Rotate keys deliberately                 | Keeps long-lived integrations safer                |

## Troubleshooting

| Problem                                | What to check first                                        |
| -------------------------------------- | ---------------------------------------------------------- |
| `401 Unauthorized`                     | Missing bearer token or invalid token                      |
| `403 Forbidden`                        | Token is valid but missing required scopes                 |
| API key creation fails                 | Ensure you are calling auth with a valid JWT               |
| Dashboard works but backend calls fail | You may be using a session flow where an API key is needed |

## Next steps

* [Quickstart: run your first task](/get-started/quickstart-run-your-first-task)
* [Use the Task Service API](/use-the-platform/use-the-task-service-api)
* [Create and manage tasks](/use-the-platform/create-and-manage-tasks)
* [Credits and billing](/about-platform/credits-and-billing) — wallet, ledger, and which scopes cover billing reads vs writes
* [HTTP errors and exceptions](/api-reference/errors) — standard and non-standard API error shapes
