# Stagera API Reference — v1

**Base URL:** `https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api`

All requests require: `Authorization: Bearer sk_live_<your_key>`

All responses return:
```json
{ "data": [...], "meta": { "org_id": "...", "page": 1, "total": 42 } }
```
Errors return:
```json
{ "error": { "code": "ERROR_CODE", "message": "Human description" } }
```

---

## Authentication

Generate API keys in **Settings → API Keys** in your Stagera account. Keys are prefixed `sk_live_` and shown only once — store them in a secrets manager or environment variable. Revocation is instant from the Settings page.

```
Authorization: Bearer sk_live_<your_key>
```

---

## Rate Limits

**100 requests per 60 seconds** per API key (rolling window). Exceeding returns `429 RATE_LIMITED`.

Tips: cache the equipment list locally, use `limit=100` to reduce pagination calls, add small delays between bulk PATCH calls.

---

## Pagination

All list endpoints accept `?page=1&limit=50` (max limit: 100). Response `meta.total` gives the total record count across all pages.

```js
// Fetch all equipment
async function fetchAllEquipment(apiKey) {
  const base = 'https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api';
  const headers = { Authorization: `Bearer ${apiKey}` };
  const all = [];
  let page = 1, total = Infinity;
  while ((page - 1) * 100 < total) {
    const res = await fetch(`${base}/v1/equipment?page=${page}&limit=100`, { headers });
    const json = await res.json();
    all.push(...json.data);
    total = json.meta.total;
    page++;
  }
  return all;
}
```

---

## Error Codes

| HTTP | Code | Meaning | Fix |
|------|------|---------|-----|
| 401 | `UNAUTHORIZED` | Missing, invalid, or revoked key | Check Authorization header; verify key not revoked |
| 403 | `FORBIDDEN` | Key lacks required scope | Regenerate key with correct scopes |
| 404 | `NOT_FOUND` | Resource doesn't exist or belongs to another org | Confirm ID is correct |
| 405 | `METHOD_NOT_ALLOWED` | HTTP method not supported on this path | Check endpoint table |
| 400 | `BAD_REQUEST` | Invalid JSON or no writable fields in PATCH | Check request body |
| 429 | `RATE_LIMITED` | 100 req/min exceeded | Back off ~60s and retry |
| 500 | `SERVER_ERROR` | Unexpected server error | Retry with backoff; contact support if persistent |

```js
const json = await res.json();
if (!res.ok) {
  switch (json.error?.code) {
    case 'UNAUTHORIZED':  // check your API key
    case 'FORBIDDEN':     // missing scope
    case 'NOT_FOUND':     // item doesn't exist
    case 'RATE_LIMITED':  // back off 60s
    default:              // log and retry
  }
}
```

---

## Scopes

| Scope | Endpoints | Use when |
|-------|-----------|----------|
| `equipment:read` | GET /equipment, GET /equipment/:id | Reading gear catalog |
| `equipment:write` | PATCH /equipment/:id | Writing dimensions/weight (requires equipment:read too) |
| `jobs:read` | GET /jobs, GET /jobs/:id | Reading job/event data |
| `crew:read` | GET /crew, GET /crew/:id | Reading crew roster |

> **Truck Packer needs both `equipment:read` AND `equipment:write`.** Write alone does not grant list access.

---

## Equipment

### GET /v1/equipment

List all equipment for the org, alphabetical by name.

**Scope:** `equipment:read`

**Query params:**
- `page` (integer, default 1)
- `limit` (integer, default 50, max 100)

```bash
curl "https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api/v1/equipment?limit=100" \
  -H "Authorization: Bearer sk_live_..."
```

**Response:**
```json
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Martin MAC 250",
      "model": "MAC 250",
      "manufacturer": "Martin",
      "serial_number": "SN12345",
      "category": "Lighting",
      "status": "available",
      "case_length_in": 48,
      "case_width_in": 24,
      "case_height_in": 12,
      "weight_lbs": 35,
      "case_type": "road_case",
      "organization_id": "org-uuid",
      "created_at": "2026-01-15T10:00:00Z",
      "updated_at": "2026-03-20T14:30:00Z"
    }
  ],
  "meta": { "org_id": "org-uuid", "page": 1, "total": 84 }
}
```

**Equipment object fields:**

| Field | Type | Writable | Description |
|-------|------|----------|-------------|
| `id` | uuid | no | Unique identifier |
| `name` | string | no | Display name |
| `model` | string\|null | no | Model name |
| `manufacturer` | string\|null | no | Brand name |
| `serial_number` | string\|null | no | Serial number |
| `category` | string\|null | no | e.g. "Lighting", "Audio", "Video" |
| `status` | string | no | `available`, `on_job`, `maintenance`, `retired` |
| `case_length_in` | number\|null | **yes** | Case exterior length, inches |
| `case_width_in` | number\|null | **yes** | Case exterior width, inches |
| `case_height_in` | number\|null | **yes** | Case exterior height, inches |
| `weight_lbs` | number\|null | **yes** | Weight in pounds |
| `case_type` | string\|null | **yes** | `road_case`, `soft_case`, `pelican` |
| `organization_id` | uuid | no | Always matches your key's org |
| `created_at` | ISO 8601 | no | Creation timestamp |
| `updated_at` | ISO 8601 | no | Last modification timestamp |

---

### GET /v1/equipment/:id

Get a single equipment record by ID.

**Scope:** `equipment:read`

```bash
curl "https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api/v1/equipment/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer sk_live_..."
```

Returns the same object shape as the list. Returns `404` if the item doesn't exist or belongs to a different org.

---

### PATCH /v1/equipment/:id

Update dimension and weight fields. Only the 5 fields below are writable — all others are managed inside Stagera. Send only the fields you want to change.

**Scope:** `equipment:write`

**Writable fields:**

| Field | Type | Description |
|-------|------|-------------|
| `case_length_in` | number | Case exterior length, inches |
| `case_width_in` | number | Case exterior width, inches |
| `case_height_in` | number | Case exterior height, inches |
| `weight_lbs` | number | Weight in pounds |
| `case_type` | string | `road_case`, `soft_case`, or `pelican` |

At least one field required. Empty body → `400 BAD_REQUEST`.

```bash
curl -X PATCH \
  "https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api/v1/equipment/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "case_length_in": 52.5,
    "case_width_in": 26.0,
    "case_height_in": 14.0,
    "weight_lbs": 38.2,
    "case_type": "road_case"
  }'
```

```js
const res = await fetch(
  `https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api/v1/equipment/${equipmentId}`,
  {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      case_length_in: 52.5,
      case_width_in: 26.0,
      case_height_in: 14.0,
      weight_lbs: 38.2,
      case_type: 'road_case',
    }),
  }
);
const { data } = await res.json();
```

Returns the full updated equipment record on success.

---

## Jobs

### GET /v1/jobs

List jobs, newest first.

**Scope:** `jobs:read`

**Query params:**
- `page` (integer, default 1)
- `limit` (integer, default 50, max 100)
- `status` (string, optional) — one of `active`, `completed`, `cancelled`, `draft`

```bash
curl "https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api/v1/jobs?status=active" \
  -H "Authorization: Bearer sk_live_..."
```

**Response:**
```json
{
  "data": [
    {
      "id": "job-uuid",
      "name": "Corporate Gala — April 2026",
      "status": "active",
      "event_date": "2026-04-18",
      "event_end_date": "2026-04-20",
      "venue": "Grand Hyatt Ballroom",
      "client_name": "Acme Corp",
      "organization_id": "org-uuid",
      "created_at": "2026-03-01T09:00:00Z",
      "updated_at": "2026-04-01T12:00:00Z"
    }
  ],
  "meta": { "org_id": "org-uuid", "page": 1, "total": 12 }
}
```

---

### GET /v1/jobs/:id

Get a single job by ID.

**Scope:** `jobs:read`

```bash
curl "https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api/v1/jobs/job-uuid" \
  -H "Authorization: Bearer sk_live_..."
```

---

## Crew

### GET /v1/crew

List crew members.

**Scope:** `crew:read`

**Query params:**
- `page` (integer, default 1)
- `limit` (integer, default 50, max 100)

```bash
curl "https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api/v1/crew?limit=100" \
  -H "Authorization: Bearer sk_live_..."
```

**Response:**
```json
{
  "data": [
    {
      "id": "crew-uuid",
      "name": "Alex Rivera",
      "email": "alex@acmeav.com",
      "role": "Lighting Director",
      "phone": "+15551234567",
      "organization_id": "org-uuid",
      "created_at": "2025-09-01T00:00:00Z",
      "updated_at": "2026-01-10T08:00:00Z"
    }
  ],
  "meta": { "org_id": "org-uuid", "page": 1, "total": 8 }
}
```

---

### GET /v1/crew/:id

Get a single crew member by ID.

**Scope:** `crew:read`

```bash
curl "https://dkgkqkkoqriebjlaufcx.supabase.co/functions/v1/api/v1/crew/crew-uuid" \
  -H "Authorization: Bearer sk_live_..."
```

---

## Truck Packer Integration Guide

### Overview

Truck Packer is an iOS LiDAR app that measures equipment case dimensions. The Stagera API lets Truck Packer push those measurements directly into your gear catalog — no CSV export or manual data entry.

**Pull phase:** Truck Packer calls `GET /v1/equipment` to load your gear list.  
**Push phase:** After a scan, Truck Packer calls `PATCH /v1/equipment/:id` with the measured dimensions.

### Setup (one-time)

1. In Stagera, go to **Settings → API Keys → New key**. Name it `Truck Packer`. Select scopes: `equipment:read` + `equipment:write`. Click **Generate key** and copy it — shown once only.
2. In the Truck Packer app: **Settings → Integrations → Stagera**. Paste the key and tap **Connect**. The app calls `GET /v1/equipment` to verify and load your gear list.
3. You should see your Stagera equipment list appear in Truck Packer. If you see a 401, double-check both scopes are selected.

### Scan workflow

1. Open a case in Truck Packer and complete the LiDAR scan.
2. Match the scan to a gear record by name from your Stagera equipment list.
3. Review the measured dimensions, then confirm to push.
4. Truck Packer sends `PATCH /v1/equipment/:id` with `case_length_in`, `case_width_in`, `case_height_in`, `weight_lbs`, and `case_type`.
5. Stagera updates instantly — visible to your whole team.

### What gets written

| Field | Source |
|-------|--------|
| `case_length_in` | LiDAR measured length |
| `case_width_in` | LiDAR measured width |
| `case_height_in` | LiDAR measured height |
| `weight_lbs` | Scale or estimated weight |
| `case_type` | Selected in Truck Packer UI |

### Troubleshooting

| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| Connection fails with 401 | Key wrong or revoked | Regenerate in Stagera Settings, re-paste in Truck Packer |
| Gear list is empty | Missing `equipment:read` scope | Create new key with both scopes |
| Scan saves but Stagera doesn't update | Missing `equipment:write` scope, or rate limit | Check scopes; if rate-limited wait 60s |
| Wrong gear matched | Similar name in list | Undo in Truck Packer, re-match, re-confirm |

Questions about the Stagera API? Email **support@stagera.ai** with the endpoint, timestamp, and error code.

---

## Changelog

| Date | Change |
|------|--------|
| 2026-03-31 | v1 launched: equipment (read/write), jobs (read), crew (read). Self-serve API keys. Rate limit: 100 req/min. |
