# Aether API V3 Frontend Integration Guide (Svelte/TypeScript) This guide explains how to update or create frontend functions to interact with the new **Aether API V3 CRUD** endpoints. V3 introduces a nested URL structure, a powerful POST-based search, and improved performance. --- ## 1. Key Differences (V2 vs V3) | Feature | CRUD V2 (Legacy) | CRUD V3 (Modern) | | --- | --- | --- | | **Base Prefix** | `/v2/crud` | `/v3/crud` | | **List Suffix** | Uses `/list` | **No suffix** (e.g., `/v3/crud/journal/`) | | **Nested Path** | Not supported in URL | **Supported** (e.g., `/v3/crud/journal/{id}/journal_entry/`) | | **View Selection**| `tbl_alt`, `mdl_alt` | **`view` parameter** (e.g., `?view=enriched`) | | **Complex Search**| Limited to GET `jp` | **POST `/search`** (Unlimited size + Hybrid params) | | **Full-Text Search**| Manual column names | **Reserved `q` property** in SearchQuery | --- ## 2. Authentication and Security (Mandatory in V3) As of January 2026, the V3 architecture enforces strict **Multi-Tenant Isolation** and **Machine Authorization**. Most requests now require two levels of validation. ### A. The "Entry Ticket" (API Key) **Mandatory for all requests.** To prevent unauthorized clients from using the API, every request must provide a valid `x-aether-api-key` in the header. This identifies the application or script (e.g., the Svelte frontend, the Flask legacy app, or an AI agent). * **Header:** `x-aether-api-key: ` * **Status Code:** `403 Forbidden` if missing, invalid, or expired. ### B. The "Visa" (Account Context) Once the API Key is validated, you must specify the context of your request. 1. **Standard User Access**: Provide the `x-account-id` (the random string ID). This restricts all results to that specific tenant. * **Header:** `x-account-id: ` 2. **Administrative Bypass**: For authorized scripts needing global access, use the bypass header. * **Header:** `x-no-account-id: bypass` 3. **Authentication Requirement**: Most V3 CRUD endpoints also require a standard Bearer token in the `Authorization` header for user-level actions. ### C. The "Bootstrap Paradox" Exception (`site_domain`) There is one critical exception to strict authentication: **`site_domain` search**. Because the frontend needs to lookup the site configuration (to know which account it's on) *before* it has an Account ID or User token, the following endpoint allows unauthenticated (**guest**) access: **Endpoint:** `POST /v3/crud/site_domain/search` This is the only V3 search allowed without an Account ID or JWT. However, it **still requires a valid API Key**. ### D. Fail-Fast on Auth Failures (401/403) To prevent unnecessary server load and looping, the frontend implements a **Fail-Fast** policy for authentication and authorization errors. * **401 Unauthorized**: Means the token is missing, invalid, or expired. * **403 Forbidden**: Means the token is valid, but you do not have permission to access the specific resource (or you attempted a cross-tenant request without bypass). * **Protocol**: If the API returns a 401 or 403, the frontend **must not retry** the request automatically. It should stop immediately and, if applicable, redirect the user to the login page or display a "Permission Denied" message. --- ## 3. Implementing V3 CRUD Functions ### A. List & Single Object (GET) Support for view selection allows fetching richer data models when needed. ```ts // Example: Get enriched journal details // GET /v3/crud/journal/{id}?view=enriched export async function get_ae_obj_v3({ api_cfg, obj_type, obj_id, view = 'default' }) { const endpoint = `/v3/crud/${obj_type}/${obj_id}`; return await get_object({ api_cfg, endpoint, params: { view } }); } ``` ### B. Advanced & Hybrid Search (POST) The `/search` endpoint combines the power of complex logical bodies with the simplicity of query parameters. ```ts export async function search_ae_obj_v3({ api_cfg, obj_type, search_query, // { q: "search term", and: [...] } enabled = 'enabled', view = 'default', for_obj_type, for_obj_id }) { const endpoint = `/v3/crud/${obj_type}/search`; // Standard filters can be passed as query params const params: any = { enabled, view }; if (for_obj_type) params.for_obj_type = for_obj_type; if (for_obj_id) params.for_obj_id = for_obj_id; return await post_object({ api_cfg, endpoint, params, data: search_query }); } ``` ### C. Standardized Global Search (`q`) Use the `q` property in your search body for a keyword search. - **Wildcard Support**: Setting `q: "%"` will bypass all text filtering and return all records (respecting only logical filters like `enable`). - **Fallback**: If the table lacks a `default_qry_str` column, the API automatically performs a `LIKE` search across all searchable fields. ```json { "q": "Annual Meeting", "and": [{ "field": "enable", "op": "eq", "value": true }] } ``` --- ## 4. Create, Update, & Delete (POST, PATCH, DELETE) V3 supports both top-level operations and nested parent/child operations. ### A. Create (POST) When creating objects, V3 strictly validates the incoming JSON against the `mdl_in` Pydantic model. ```ts // POST /v3/crud/{obj_type}/ export async function create_ae_obj_v3({ api_cfg, obj_type, data }) { const endpoint = `/v3/crud/${obj_type}/`; return await post_object({ api_cfg, endpoint, data }); } // POST /v3/crud/{parent_obj_type}/{parent_obj_id}/{child_obj_type}/ // Note: Parent ID is automatically injected into the child record. export async function create_nested_obj_v3({ api_cfg, parent_type, parent_id, child_type, data }) { const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/`; return await post_object({ api_cfg, endpoint, data }); } ``` ### B. Update (PATCH) V3 uses `PATCH` for partial updates. Only the fields provided in the body will be modified in the database. ```ts // PATCH /v3/crud/{obj_type}/{obj_id} export async function update_ae_obj_v3({ api_cfg, obj_type, obj_id, data }) { const endpoint = `/v3/crud/${obj_type}/${obj_id}`; return await patch_object({ api_cfg, endpoint, data }); } ``` ### C. Delete (DELETE) The `DELETE` method is used for removal. The backend may implement soft-delete (hide/disable) depending on the configuration. ```ts // DELETE /v3/crud/{obj_type}/{obj_id} export async function delete_ae_obj_v3({ api_cfg, obj_type, obj_id }) { const endpoint = `/v3/crud/${obj_type}/${obj_id}`; return await delete_object({ api_cfg, endpoint }); } ``` --- ## 5. Specialized & Context Endpoints ### A. Context Resolution (FQDN) Used during initial load to find the `account_id` associated with the domain. **Legacy Method:** `GET /crud/site/domain/{fqdn}?use_alt_table=true&use_alt_base=true` **Modern V3 Method (Preferred):** `POST /v3/crud/site_domain/search` with `{ "q": "your-domain.com" }` ### B. Schema Discovery **Path**: `GET /v3/crud/{obj_type}/schema` Used for developer tools or dynamic UI builders to understand the structure of an AE object. ```ts // GET /v3/crud/account/schema export async function get_obj_schema_v3({ api_cfg, obj_type }) { const endpoint = `/v3/crud/${obj_type}/schema`; return await get_object({ api_cfg, endpoint }); } ``` --- ## 6. Secure File Downloads (URL Parameter) For `hosted_file` and `event_file`, browsers often need to download files without complex header modifications. In these cases, you can pass the JWT directly in the URL. ```ts // Example: Creating a secure download link for a browser // GET /v3/crud/hosted_file/{id}/?jwt={token} const downloadUrl = `${BASE_URL}/v3/crud/hosted_file/${fileId}/?jwt=${jwtToken}`; ``` --- ## 7. The "ID Vision" Standard (2026 Refactor) V3 has moved to a **String-Only ID Vision**. In this paradigm, the frontend (SvelteKit) and external agents NEVER handle or store internal database integers. ### A. Automatic ID Mapping (Output) All V3 endpoints now automatically rename internal `id_random` fields to clean, short names in the JSON response. - **`id`**: The unique random identifier for the primary record. - **`account_id`**: The unique random identifier for the parent account. - **`site_id`, `person_id`, etc**: All related IDs follow this clean naming. **Example Response:** ```json { "id": "OGQK-02-04-94", "name": "Scott's Journal", "account_id": "nqOzejLCDXM" } ``` ### B. Intelligent Mapping (Input/Search) The backend is now "Vision Aware." You can send string IDs back to the API using the same clean names. - **Search Filters**: You can filter by `account_id` using the random string value. The API handles the mapping to the DB integer automatically. - **Create/Update**: You can send `account_id: "nqOzejLCDXM"` in a payload. The `sanitize_payload` logic resolves this to the correct integer before saving. **Preferred Filter Pattern:** ```json { "and": [{ "field": "account_id", "op": "eq", "value": "nqOzejLCDXM" }] } ``` --- ## 8. Standard Patterns & Common Pitfalls (2026 Update) ### A. Permissive Update Mode (Available) **Usage:** High-performance state syncing. To simplify frontend state management, V3 supports a **Permissive Update Mode**. When enabled, the backend will **ignore** extra fields in your `PATCH` or `POST` payload (like `_processed_at`, `created_on`, or UI-specific keys) instead of returning a `400 Bad Request`. - **Header:** `x-ae-ignore-extra-fields: true` **When to use:** - When sending back an object fetched via `GET` that contains read-only technical metadata. - When the frontend adds temporary "state" keys to objects that shouldn't be saved to the DB. --- ### B. Handle 'NULL' vs '0' for Booleans **Pitfall:** Fields like `hide` or `priority` can be `NULL` in the database. **Standard:** Ensure your synchronization logic in components handles `null`, `undefined`, and `0` identically for boolean checks to prevent "ghost" changes being detected by Svelte 5. --- ### C. Structured Error Handling (Rich Bubbling) V3 provides machine-readable error objects in the `meta.details` field for most failures (400/500). Instead of parsing strings, your error handler should check the `category` and `code`. **Example Error Response:** ```json { "meta": { "success": false, "status_code": 400, "status_message": "Failed to update object.", "details": { "category": "database_schema", "code": 1054, "message": "Unknown column 'unauthorized_field' in 'SET'", "recoverable": false } } } ``` **Common Categories:** - **`database_duplicate`**: Attempted to create a record with a non-unique value (Code 1062). - **`database_constraint`**: Foreign key or integrity violation (Codes 1451, 1452). - **`database_schema`**: Invalid column name or table configuration (Codes 1054, 1146). - **`database_connection`**: Temporary DB unavailability (`recoverable: true`). - **`validation`**: Pydantic validation failed (Check `details` for field-specific errors).