# Aether API V3 Frontend Integration Guide (Svelte/TypeScript)

This guide defines the standards for interacting with the **Aether API V3 CRUD** and **Action** endpoints.

---

## 1. Authentication and Security (Mandatory)

V3 architecture enforces strict **Multi-Tenant Isolation** and **Machine Authorization**. Requests require two levels of validation.

### A. The "Entry Ticket" (API Key)
**Mandatory for all requests.** identifies the application or client.
*   **Header:** `x-aether-api-key: <your_app_key>`
*   **Status Code:** `403 Forbidden` if missing or invalid.

### B. The "Visa" (Account Context)
Required for any non-public data (Journals, Badges, Users, etc.).
1.  **Standard Access**: Provide the `x-account-id` (the random string ID).
    *   **Header:** `x-account-id: <account_id>`
2.  **Administrative Bypass**: For authorized scripts needing global access.
    *   **Header:** `x-no-account-id: bypass`
3.  **Token Access**: Provide a **JWT** in the query string.
    *   **Query Param:** `?jwt=<token>`

> [!CAUTION]
> **UNSUPPORTED HEADERS:** The header `x-aether-api-token` is **NOT recognized** by the V3 API. If you send it, the backend will treat you as a guest and block access to private data.

---

## 2. Bootstrapping (The FQDN Handshake)

When the frontend first loads and doesn't know the `account_id`, it performs a "handshake" using its domain name.

**Endpoint:** `POST /v3/crud/site_domain/search`
**Body:**
```json
{
  "and": [
    { "field": "fqdn", "op": "eq", "value": "demo.oneskyit.com" }
  ]
}
```
**Results:**
*   Returns 200 + a list containing the `account_id` (random string ID) and `site_id` (random string ID).
*   ** デザイン Choice:** If the domain is not found, it returns **200 OK with an empty list `[]`**. It is NOT a 404.

---

## 3. Standard CRUD Patterns

### A. GET by ID
Used when the ID is known.
*   **Endpoint:** `GET /v3/crud/{obj_type}/{id}`
*   **Security:** Returns 403 if the record doesn't belong to your `x-account-id`.

### B. POST Search
The primary way to retrieve data.
*   **Endpoint:** `POST /v3/crud/{obj_type}/search`
*   **Security:** Automatically filters results to only show records belonging to your `x-account-id`. If no account context is provided, it will return **0 records** for private objects.

### C. POST Create / PATCH Update
Modify data in the system.
*   **Endpoints:**
    *   `POST /v3/crud/{obj_type}/`
    *   `PATCH /v3/crud/{obj_type}/{id}`
*   **Strict Mode (Default):** The API validates your payload against the Pydantic model. If you send fields that do not exist in the model, the database might return a 400 "Unknown column" error.
*   **Permissive Mode (Header):** To allow the frontend to send "extra" fields (like local UI state) without causing errors, use the following header:
    *   **Header:** `x-ae-ignore-extra-fields: true`
    *   **Behavior:** When set to `true`, the backend will automatically strip any fields from the payload that are not defined in the object's model before attempting to save to the database.

---

## 4. Flat vs Nested URL Paths

This is the single most common source of mock errors in Playwright tests. The V3 API has two URL structures and the right one to use depends on the object type and the operation.

### Always-Flat Objects

These object types use flat paths for **every** operation — GET, list, search, create, update, delete. They are never children in a nested URL.

**Core modules:**
`account`, `activity_log`, `address`, `contact`, `hosted_file`, `organization`, `page`, `person`, `site`, `user`

**Other modules:**
`archive`, `event`, `journal`, `post`

All operations for these objects use the flat helpers:
| Operation | Function | URL pattern |
|---|---|---|
| GET single | `api.get_ae_obj_v3` | `/v3/crud/{obj_type}/{id}` |
| GET list | `api.get_ae_obj_li_v3` | `/v3/crud/{obj_type}/?for_obj_id=...` |
| Search | `api.search_ae_obj_v3` | `/v3/crud/{obj_type}/search` |
| Create | `api.create_ae_obj_v3` | `/v3/crud/{obj_type}/` |
| Update | `api.patch_ae_obj_v3` | `/v3/crud/{obj_type}/{id}` |
| Delete | `api.delete_ae_obj_v3` | `/v3/crud/{obj_type}/{id}` |

### Nested Objects (Event Sub-objects)

Objects that belong to a parent (e.g. `event_badge`, `event_session`, `event_badge_template`, `event_file`, `event_presenter`, `event_presentation`, `event_device`, `event_location`) use **nested paths for mutations** but **flat paths for reads**.

| Operation | Function | URL pattern |
|---|---|---|
| GET single | `api.get_ae_obj_v3` | `/v3/crud/{obj_type}/{id}` ← **flat** |
| GET list | `api.get_ae_obj_li_v3` | `/v3/crud/{obj_type}/?for_obj_id=...` ← **flat** |
| Search | `api.search_ae_obj_v3` | `/v3/crud/{obj_type}/search` ← **flat** |
| Create | `api.create_nested_obj_v3` | `/v3/crud/{parent_type}/{parent_id}/{child_type}/` ← **nested** |
| Update | `api.update_nested_obj_v3` | `/v3/crud/{parent_type}/{parent_id}/{child_type}/{child_id}` ← **nested** |
| Delete | `api.delete_ae_obj_v3` | `/v3/crud/{obj_type}/{id}` ← **flat** |

**Example — `event_badge` under event `abc123`:**
```
GET    /v3/crud/event_badge/badge-xyz          ← flat (get_ae_obj_v3)
GET    /v3/crud/event_badge/?for_obj_id=abc123 ← flat (get_ae_obj_li_v3)
POST   /v3/crud/event_badge/search             ← flat (search_ae_obj_v3)
POST   /v3/crud/event/abc123/event_badge/      ← nested (create_nested_obj_v3)
PATCH  /v3/crud/event/abc123/event_badge/badge-xyz  ← nested (update_nested_obj_v3)
DELETE /v3/crud/event_badge/badge-xyz          ← flat (delete_ae_obj_v3)
```

### Playwright Mock Rule of Thumb

Since all reads are flat, the most common mock patterns are:
```typescript
// ✅ Search (always flat)
url.includes('/v3/crud/event_badge/search') && method === 'POST'

// ✅ List (always flat, filter by query param)
url.includes('/v3/crud/event_badge/') && url.includes('for_obj_id') && method === 'GET'

// ✅ GET single (always flat)
url.includes('/v3/crud/event_badge/badge-xyz') && method === 'GET'

// ✅ Create (nested for event sub-objects)
url.match(/\/v3\/crud\/event\/[^/]+\/event_badge\/$/) && method === 'POST'

// ✅ Update (nested for event sub-objects)
url.match(/\/v3\/crud\/event\/[^/]+\/event_badge\/badge-xyz/) && method === 'PATCH'
```

---

## 5. V3 Uniform Lookup System
<!-- was section 4 -->

The V3 Lookup system provides a hierarchical, deduplicated interface for standardized tables (Countries, Timezones, etc.). It supports global defaults, account overrides, and site-specific whitelisting.

### A. List Lookups
Retrieve a ranked and filtered list of lookup items.
*   **Endpoint:** `GET /v3/lookup/{lu_type}/list`
*   **Available Types:** `country`, `country_subdivision`, `time_zone`
*   **Parameters:**
    *   `site_id` (Optional): Random ID of the site to apply a **Whitelist Policy**.
    *   `only_priority` (Optional): Set to `true` to return only high-priority items (e.g., common time zones).
    *   `for_type` / `for_id` (Optional): Context for object-specific overrides.
    *   `include_disabled` (Optional): Set to `true` to see shadowed/disabled records.

### B. Resolve Identity
Resolves a string (code, group, or name) to a single record.
*   **Endpoint:** `GET /v3/lookup/{lu_type}/resolve?q=VALUE`
*   **Usage:** Use this when you have an external code (e.g., ISO "US") and need the full Aether record.

### C. Site Whitelist Policy
To limit lookups for a specific site, add a `lookup_policy` to the `site.cfg_json` field.
**Schema:**
```json
{
  "lookup_policy": {
    "country": ["US", "CA", "GB"],
    "time_zone": ["America/New_York"]
  }
}
```
*Note: Whitelist values must match the `group` field in the database.*

---

## 6. Event File Data Retrieval (Hosted Files)

Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file`). The Hosted File itself is a metadata record for binary content (files), which is accessed via separate Action endpoints (e.g., `/v3/action/hosted_file/download`). This API endpoint provides metadata about the associated hosted file. To retrieve this additional metadata:

*   **Endpoint:** `GET /v3/crud/event_file/{event_file_id}`
*   **Query Parameter:** Add `inc_hosted_file=true`
    *   Example: `/v3/crud/event_file/<event_file_id>?inc_hosted_file=true`

**Response Impact:**

1.  **Top-Level Convenience Fields:** The response will include top-level fields for commonly needed hosted file data. These are populated directly from the SQL view via JOINs.
    *   `hosted_file_hash_sha256` (string)
    *   `hosted_file_subdirectory_path` (string)
    *   `hosted_file_content_type` (string)
    *   `hosted_file_size` (string - in bytes)
2.  **Nested Hosted File Object:** A full `hosted_file` object will be nested under the `hosted_file` key. This object (`Hosted_File_Base` model) will contain all its standard fields, including `id` (random string ID), `hash_sha256`, `content_type`, `size`, etc.

---

## 7. Troubleshooting 403 Forbidden

If you receive a 403 on a valid ID:
1.  Verify `x-aether-api-key` is correct.
2.  Ensure you are sending `x-account-id` and NOT `x-aether-api-token`.
3.  Verify the record actually belongs to the account ID you are sending.
4.  Check if the object is marked `public_read: True` in the registry. (Posts and Archive Content allow guest access; Journals and Badges do not).
