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

# HTTP API

> For any runtime that can POST JSON.

Voight accepts raw HTTP `POST` from any runtime that speaks JSON over HTTPS — curl, Python, Go, Rust, browsers, embedded devices. No dependency required.

## Endpoint

```
POST https://api.voight.xyz/v1/events
```

## Authentication

```
Authorization: Bearer vk_YOUR_API_KEY
Content-Type: application/json
```

Generate your `vk_` key at [voight.xyz/dashboard/settings](https://voight.xyz/dashboard/settings).

## Minimal payload

```bash theme={null}
curl https://api.voight.xyz/v1/events \
  -H "authorization: Bearer $VOIGHT_KEY" \
  -H "content-type: application/json" \
  -d '{
    "agentId": "trading-bot.sol",
    "type": "tx",
    "toolExecuted": "jupiter.swap",
    "transaction": "4zK...9Fp"
  }'
```

Response:

```json theme={null}
{
  "id": "evt_clz3...",
  "accepted": true,
  "agentId": "cmoq...",
  "eventId": "evt_clz3..."
}
```

The server returns the resolved `agentId` (a CUID) — store it locally if you want subsequent events to skip label resolution.

## Full payload schema

```json theme={null}
{
  "agentId": "trading-bot.sol",
  "timestamp": "2026-05-12T14:02:54.084Z",
  "type": "tx",
  "input": { "prompt": "...", "context": { } },
  "reasoning": "free-form trace",
  "toolsConsidered": ["jupiter.swap", "orca.swap"],
  "toolExecuted": "jupiter.swap",
  "transaction": "4zK...9Fp",
  "amount": { "token": "SOL", "value": 1.5 },
  "outcome": "success",
  "durationMs": 184,
  "errorMessage": "optional, when outcome=failed",
  "model": "claude-opus-4-7",
  "metadata": {
    "source": "my-trading-bot",
    "strategy": "mean-reversion",
    "tokensBreakdown": {
      "inputBase": 50,
      "cacheCreation": 100,
      "cacheRead": 850,
      "output": 200
    },
    "privacyLevel": "standard"
  }
}
```

All fields except `agentId` are optional. The server defaults `type: 'decision'` if omitted.

## Privacy on the HTTP path

The HTTP path **does not** apply PII scrubbing — that's a client-side operation that requires the SDK. If you're calling HTTP directly, you're responsible for scrubbing sensitive content before it leaves your machine.

To have the dashboard render audit chips on HTTP events, include `metadata.privacyLevel: 'minimal' | 'standard' | 'full'`. The scrubbing itself is your responsibility on this path.

If you're already on Node, [library mode](/sdk/library-mode) exposes `scrubPii()` directly — same patterns, no hook handler required.

## Examples

### Python

```python theme={null}
import requests
import os

def log_event(payload):
    response = requests.post(
        "https://api.voight.xyz/v1/events",
        headers={
            "authorization": f"Bearer {os.environ['VOIGHT_KEY']}",
            "content-type": "application/json",
        },
        json=payload,
        timeout=5,
    )
    response.raise_for_status()
    return response.json()

log_event({
    "agentId": "py-bot",
    "type": "action",
    "toolExecuted": "fetch_market_data",
    "outcome": "success",
    "durationMs": 412,
})
```

### Go

```go theme={null}
package main

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

func logEvent(payload map[string]any) error {
    body, _ := json.Marshal(payload)
    req, _ := http.NewRequest("POST",
        "https://api.voight.xyz/v1/events",
        bytes.NewBuffer(body))
    req.Header.Set("Authorization", "Bearer "+os.Getenv("VOIGHT_KEY"))
    req.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    return nil
}
```

### Rust

```rust theme={null}
use reqwest::Client;
use serde_json::json;

async fn log_event(payload: serde_json::Value) -> reqwest::Result<()> {
    let client = Client::new();
    let api_key = std::env::var("VOIGHT_KEY").unwrap();
    client.post("https://api.voight.xyz/v1/events")
        .bearer_auth(&api_key)
        .json(&payload)
        .send()
        .await?
        .error_for_status()?;
    Ok(())
}
```

### Browser / fetch

```ts theme={null}
await fetch('https://api.voight.xyz/v1/events', {
  method: 'POST',
  headers: {
    'authorization': `Bearer ${VOIGHT_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    agentId: 'browser-agent',
    reasoning: 'user clicked the swap button',
  }),
})
```

CORS is configured to allow `https://voight.xyz` and `https://www.voight.xyz`. For other origins, you'll need server-side proxying.

## Status codes

| Code                    | Meaning                                                                  |
| ----------------------- | ------------------------------------------------------------------------ |
| `202 Accepted`          | Event received and persisted (returns `{ id, accepted: true, agentId }`) |
| `400 Bad Request`       | Payload failed Zod validation (response includes details)                |
| `401 Unauthorized`      | Missing or invalid API key                                               |
| `410 Gone`              | The agent has been soft-deleted, ingestion blocked                       |
| `429 Too Many Requests` | Rate-limited (honor `Retry-After` header)                                |
| `500 Server Error`      | Backend issue — retry with exponential backoff                           |

## Rate limits

Per pricing tier:

| Tier       | Events / month |
| ---------- | -------------- |
| Free       | 10,000         |
| Pro        | 300,000        |
| Enterprise | Unlimited      |

Quotas are advisory today, enforced server-side starting v1.0. Burst rate-limiting (per-second) is in place to prevent abuse.

## Next

* [Library mode](/sdk/library-mode) — for JS/TS; adds retry, typed errors, and PII scrubbing
* [`POST /v1/events` reference](/api-reference/events) — full schema with every field documented
