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.
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 |
cURL
Go
TypeScript
Python
Rust
export AUTH_URL="https://api.webscraping.titannet.io"
export TASK_SERVICE_URL="https://api.webscraping.titannet.io"
// export AUTH_URL / TASK_SERVICE_URL in your shell, or set in code:
// os.Setenv("AUTH_URL", "https://api.webscraping.titannet.io")
process.env.AUTH_URL ??= "https://api.webscraping.titannet.io";
process.env.TASK_SERVICE_URL ??= "https://api.webscraping.titannet.io";
import os
os.environ.setdefault("AUTH_URL", "https://api.webscraping.titannet.io")
os.environ.setdefault("TASK_SERVICE_URL", "https://api.webscraping.titannet.io")
// std::env::set_var("AUTH_URL", "https://api.webscraping.titannet.io");
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
cURL
Go
TypeScript
Python
Rust
curl -sS -X POST "$AUTH_URL/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{ "email": "you@example.com", "password": "your-password" }'
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))
}
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());
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())
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"));
Step 2: retrieve the active session
cURL
Go
TypeScript
Python
Rust
curl -sS "$AUTH_URL/api/v1/auth/session" \
-H "Authorization: Bearer $JWT_TOKEN"
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))
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());
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())
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"));
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
cURL
Go
TypeScript
Python
Rust
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"]
}'
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))
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());
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())
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"));
Example response:
{
"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:
cURL
Go
TypeScript
Python
Rust
curl -H "Authorization: Bearer $TITAN_TOKEN" "$TASK_SERVICE_URL/api/v1/tasks"
req.Header.Set("Authorization", "Bearer "+os.Getenv("TITAN_TOKEN"))
const headers = {
Authorization: `Bearer ${process.env.TITAN_TOKEN}`,
"Content-Type": "application/json",
};
headers = {
"Authorization": f"Bearer {os.environ['TITAN_TOKEN']}",
"Content-Type": "application/json",
}
.set(
"Authorization",
&format!("Bearer {}", std::env::var("TITAN_TOKEN").expect("TITAN_TOKEN")),
)
That token may be either:
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