---
title: API Overview
description: Authenticate, route requests to nodes, and integrate Sencho into your CI/CD pipelines.
---

Sencho exposes a REST API for automating stack deployments, managing webhooks, monitoring fleet health, and integrating with CI/CD pipelines. Since Sencho is self-hosted, the API base URL is your own instance.

## Base URL

```
https://your-sencho-instance:1852/api
```

Replace with your actual Sencho host and port. All API paths are prefixed with `/api`.

## Authentication

Authenticated endpoints require a **Bearer token** in the `Authorization` header. Generate API tokens from **Settings > API Tokens** in the Sencho dashboard.

```bash
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://your-sencho-instance:1852/api/stacks
```

API tokens are 56-character opaque strings that begin with `sen_sk_`, for example `sen_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`. The prefix makes them easy to identify in logs and is recognized by secret-scanning tools (GitHub, TruffleHog, GitGuardian).

### Token scopes

Every token is created with one of three permission levels:

| Scope | Allowed actions |
|-------|----------------|
| **Read Only** | `GET` requests only — view stacks, containers, and fleet status |
| **Deploy Only** | Everything in Read Only, plus stack operations: deploy, down, restart, stop, start, update |
| **Full Admin** | Full stack, container, and node management — all read and write operations |

Regardless of scope, all API tokens are blocked from managing other tokens, users, licenses, and SSO configuration.

## Node routing

Sencho manages multiple nodes (local and remote). To target a specific node, include one of:

- **Header:** `x-node-id: 1`
- **Query parameter:** `?nodeId=1`

If omitted, the request targets the default node.

```bash
# Target node ID 2
curl -H "Authorization: Bearer TOKEN" \
  -H "x-node-id: 2" \
  https://your-sencho-instance:1852/api/stacks
```

## Error format

All error responses follow the same shape:

```json
{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_CODE"
}
```

The `code` field is present for specific error types:

| Code | Meaning |
|------|---------|
| `PAID_REQUIRED` | Endpoint requires an Admiral license |
| `SCOPE_DENIED` | API token scope does not allow this operation |

## Input validation

All endpoints that accept a `stackName` path parameter validate it against the pattern `^[a-zA-Z0-9_-]+$`. Stack names may only contain **alphanumeric characters, hyphens, and underscores**. Requests with invalid stack names are rejected immediately:

```json
// 400 Bad Request
{
  "error": "Invalid stack name"
}
```

This applies to all stack endpoints: read, write, deploy, down, restart, stop, start, update, rollback, backup, delete, and container listing.

## Rate limiting

Sencho uses a tiered rate limiting system that balances security with usability:

| Tier | Limit | Applies to | Keyed by |
|------|-------|------------|----------|
| **Polling** | 300/min | Dashboard status endpoints (`/stats`, `/system/stats`, `/stacks/statuses`, etc.) | User session or API token |
| **Standard** | 200/min | All other API endpoints | User session or API token |
| **Webhooks** | 500/min | `POST /webhooks/:id/trigger` | IP address |
| **Auth** | 5-10/15min | Login, setup, SSO endpoints | IP address |

Authenticated requests are rate-limited per credential (per user session, or per API token). This prevents teams behind shared NAT or VPN from pooling rate limit budgets, and lets two API-token-driven automations under the same account run on independent budgets. Unauthenticated requests fall back to IP-based limiting.

Internal node-to-node traffic (requests authenticated with node proxy tokens) bypasses rate limiting entirely.

When exceeded, the server responds with `429 Too Many Requests`:

```json
{
  "error": "Too many requests. Please try again shortly."
}
```

Standard rate limit headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`) are included in all API responses, so clients can self-throttle accordingly.

The default limits can be tuned via environment variables (`API_RATE_LIMIT`, `API_POLLING_RATE_LIMIT`). See [Self-Hosting](/operations/self-hosting) for details.

## License tier requirements

Some endpoints are gated by license tier:

| Tier | Gated features |
|------|---------------|
| **Admiral** | Private registries (AWS ECR) |

Requests to gated endpoints on Community return `403` with the `PAID_REQUIRED` error code.

## WebSocket endpoints

Sencho also provides real-time streaming via WebSocket connections. These are not part of the OpenAPI spec (OpenAPI does not support WebSocket protocols) but are documented here for completeness.

### Stack log streaming

Stream live logs from a stack's containers.

**URL:** `wss://your-sencho-instance:1852/api/stacks/{stackName}/logs?nodeId={nodeId}`

**Authentication:** Pass the token as a cookie (`sencho_token`) or Bearer token. For WebSocket connections, authentication is verified during the upgrade handshake.

<CodeGroup>

```javascript Node.js
import WebSocket from "ws";

const ws = new WebSocket(
  "wss://your-sencho-instance:1852/api/stacks/my-app/logs",
  { headers: { Cookie: "sencho_token=YOUR_JWT" } }
);

ws.on("message", (data) => {
  console.log(data.toString());
});
```

```bash cURL (upgrade check)
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Cookie: sencho_token=YOUR_JWT" \
  https://your-sencho-instance:1852/api/stacks/my-app/logs
```

</CodeGroup>

### Container exec

Open an interactive shell session inside a running container.

**URL:** `wss://your-sencho-instance:1852/ws`

**Authentication:** Cookie-based JWT only. API tokens with `read-only` or `deploy-only` scope are blocked.

<CodeGroup>

```javascript Node.js
import WebSocket from "ws";

const ws = new WebSocket("wss://your-sencho-instance:1852/ws", {
  headers: { Cookie: "sencho_token=YOUR_JWT" },
});

ws.on("open", () => {
  // Initiate exec session
  ws.send(
    JSON.stringify({
      action: "execContainer",
      containerId: "abc123...",
    })
  );
});

ws.on("message", (data) => {
  // Terminal output
  process.stdout.write(data.toString());
});

// Send terminal input
ws.send(JSON.stringify({ type: "input", data: "ls -la\n" }));

// Resize terminal
ws.send(JSON.stringify({ type: "resize", cols: 120, rows: 40 }));
```

</CodeGroup>

<Warning>
  Container exec requires an **admin** role. API tokens with `read-only` or `deploy-only` scope are blocked, as are node proxy tokens.
</Warning>
