# https://compresr.ai/docs/api-reference/conventions

> Human-readable page: https://compresr.ai/docs/api-reference/conventions

Shared contract for every Compresr REST endpoint: base URL, auth header, request format, response envelope, status codes, rate limits, and versioning. Per-endpoint pages only document what's specific to that route.

## Base URL

All Compresr endpoints are served from a single host over HTTPS. Plain HTTP is not supported; requests to `http://api.compresr.ai` are rejected at the edge.

```text
https://api.compresr.ai
```

Endpoints are mounted under `/api`. For example, the question-specific compression endpoint is `https://api.compresr.ai/api/compress/question-specific/`.

## Authentication

Authenticated endpoints expect an API key in the `X-API-Key` header. Keys are prefixed `cmp_` and are issued from the [dashboard](/dashboard/api-keys). The Python and TypeScript SDKs attach the header for you when you pass the key to the client constructor.

```python
import os
from compresr import CompressionClient

# X-API-Key is sent automatically on every request.
client = CompressionClient(api_key=os.environ["COMPRESR_API_KEY"])
```

**TypeScript**

```typescript
import { CompressionClient } from '@compresr/sdk';

// X-API-Key is sent automatically on every request.
const client = new CompressionClient({ apiKey: process.env.COMPRESR_API_KEY! });
```

**cURL**

```bash
curl https://api.compresr.ai/api/compress/question-specific/ \
  -H "X-API-Key: $COMPRESR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "context": "...", "query": "...", "compression_model_name": "{{DEFAULT_MODEL}}" }'
```

See [Authentication](/docs/authentication) for the full security model: rotation, expiry, and per-key budgets.

## Content type

Requests with a body use `Content-Type: application/json`. Responses are always JSON (except streaming endpoints, which use `text/event-stream`, see below). The SDKs set the request header for you; with cURL you must set it explicitly.

```python
# Content-Type: application/json is set by the SDK.
result = client.compress(
    context="...",
    query="...",
    compression_model_name="{{DEFAULT_MODEL}}",
)
```

**TypeScript**

```typescript
// Content-Type: application/json is set by the SDK.
const result = await client.compress({
  context: '...',
  query: '...',
  compressionModelName: '{{DEFAULT_MODEL}}',
});
```

**cURL**

```bash
curl -X POST https://api.compresr.ai/api/compress/question-specific/ \
  -H "X-API-Key: $COMPRESR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "context": "...", "query": "...", "compression_model_name": "{{DEFAULT_MODEL}}" }'
```

Field names use `snake_case` (`compression_model_name`, `target_compression_ratio`). The TypeScript SDK exposes the same fields as `camelCase` and translates them on the wire.

## Response envelope

Every endpoint returns the same envelope. `data` carries the success payload; `error` is present only on failure. `success` is the boolean discriminant.

| Field | Type | Description |
| --- | --- | --- |
| `success` | boolean | true when the request succeeded, false otherwise. |
| `data` | object \| null | Endpoint-specific payload. Null on error. |
| `error` | object \| null | Present only on failure. Null on success. |
| `error.code` | string | Stable machine-readable error code (e.g. invalid_api_key). Switch on this in client code. |
| `error.message` | string | Human-readable message safe to log. Wording may change. |

A concrete error response looks like:

```json
{
  "success": false,
  "data": null,
  "error": {
    "code": "invalid_api_key",
    "message": "The provided API key does not exist or has been revoked."
  }
}
```

Streaming endpoints (paths ending in `/stream`) do not use this envelope; they return Server-Sent Events. See [`POST /compress/question-specific/stream`](/docs/api-reference/compress-qs-stream) for the chunk shape.

## Status codes

| Status | Description |
| --- | --- |
| 200 | Request succeeded. data is populated, error is null. |
| 400 | Malformed JSON or missing required field. |
| 401 | Missing, invalid, or expired `X-API-Key`. |
| 403 | Key is valid but lacks access to this resource or tier. |
| 404 | No route matched the request path or method. |
| 422 | Field values failed validation (e.g. empty `query`, `target_compression_ratio` out of range). |
| 429 | Tier rate limit hit. Honour `Retry-After`. |
| 500 | Unexpected server failure. Safe to retry once with a short delay. |
| 503 | Upstream circuit breaker open. Honour `Retry-After`. |

See [Error codes](/docs/api-reference/errors) for the full reference, the stable `error.code` values per status, and recovery guidance.

## Rate limits

Every endpoint is subject to per-tier per-minute and per-day quotas. Hitting either returns `429 Too Many Requests` with a `Retry-After` header in seconds. The SDKs surface this as a typed `RateLimitError` you can catch and back off on; with cURL you read the header directly.

```python
import time
from compresr import RateLimitError

try:
    result = client.compress(context="...", query="...", compression_model_name="{{DEFAULT_MODEL}}")
except RateLimitError as exc:
    time.sleep(exc.retry_after or 1)
    # ... then retry
```

**TypeScript**

```typescript
import { RateLimitError } from '@compresr/sdk';

try {
  await client.compress({ context: '...', query: '...', compressionModelName: '{{DEFAULT_MODEL}}' });
} catch (err) {
  if (err instanceof RateLimitError) {
    await new Promise((r) => setTimeout(r, (err.retryAfter ?? 1) * 1000));
    // ... then retry
  } else {
    throw err;
  }
}
```

**cURL**

```bash
# Inspect the Retry-After header on a 429 response.
curl -i -X POST https://api.compresr.ai/api/compress/question-specific/ \
  -H "X-API-Key: $COMPRESR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "context": "...", "query": "...", "compression_model_name": "{{DEFAULT_MODEL}}" }' \
  | grep -i '^retry-after:'
```

See [Rate limits](/docs/api-reference/rate-limits) for the canonical headers, tier table, and recommended exponential-backoff pattern.

## Versioning

The API is versioned in the URL prefix once we introduce a breaking change. Today the prefix is implicit: current routes live under `/api/...` with no explicit `/v1/` segment, and clients should not depend on one. When a v2 surface ships it will be served alongside v1 from a versioned prefix, and v1 will continue to operate for a documented deprecation window. Subscribe to the [changelog](/docs/changelog) for breaking-change notices.
