16 KiB
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 Forbiddenif missing or invalid.
B. The "Visa" (Account Context)
Required for any non-public data (Journals, Badges, Users, etc.).
- Standard Access: Provide the
x-account-id(the random string ID).- Header:
x-account-id: <account_id>
- Header:
- Administrative Bypass: For authorized scripts needing global access.
- Header:
x-no-account-id: bypass
- Header:
- Token Access: Provide a JWT in the query string.
- Query Param:
?jwt=<token>
- Query Param:
Caution
UNSUPPORTED HEADERS: The header
x-aether-api-tokenis 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:
{
"and": [
{ "field": "fqdn", "op": "eq", "value": "demo.oneskyit.com" }
]
}
Results:
- Returns 200 + a list containing the
account_id(random string ID) andsite_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.
- Header:
4. V3 Uniform Lookup System
The V3 Lookup system provides a hierarchical, deduplicated interface for standardized reference tables (Countries, Timezones, etc.). It supports global defaults, account-level overrides, and object-level overrides, with optional site-specific whitelisting.
How the hierarchy works
Each lookup table (lu_v3_country, lu_v3_time_zone, etc.) can hold multiple rows for the same logical item at different scopes:
| Scope | account_id |
for_type / for_id |
Wins over |
|---|---|---|---|
| Global default | NULL |
NULL / NULL |
nothing |
| Account override | set | NULL / NULL |
Global default |
| Object override | set | set | Account override + Global default |
The API uses ROW_NUMBER() PARTITION BY group to collapse all rows for the same item down to the single highest-priority winner before returning results. group is the identity key — it is what makes two rows "the same item competing for priority."
Important
The
groupfield is not a display label. It is the deduplication key. Each lookup type uses a different natural key forgroup:
Lookup type groupvalueExample countryISO alpha-2 code "US","CA","GB"country_subdivisionsubdivision code "US-NY","CA-ON"time_zoneIANA timezone name "America/New_York","US/Eastern"For
time_zone,groupandnamemust always be identical — there is no concept of "override all US timezones as a group." Each timezone is its own identity.
A. List Lookups
Retrieve the deduplicated, ranked list for a lookup type.
- Endpoint:
GET /v3/lookup/{lu_type}/list - Available Types:
country,country_subdivision,time_zone - Parameters:
site_id(Optional): Random ID of the site — applies a Whitelist Policy (see §C).only_priority(Optional):truereturns onlypriority=1items (e.g., common time zones).for_type/for_id(Optional): Object context — activates object-level override matching.include_disabled(Optional):trueincludes shadowed/disabled records (useful for admin views).
Frontend keying: Always key Svelte {#each} blocks on group, not id or name. group is guaranteed unique in the response. Keying on id will break if an account override wins (different id, same logical item).
B. Resolve Identity
Resolves a string to a single lookup record.
- Endpoint:
GET /v3/lookup/{lu_type}/resolve?q=VALUE - Usage: Use when you have an external code (e.g., ISO
"US") and need the full Aether record. Scansname,group, and other identity fields.
C. Site Whitelist Policy
To restrict which lookup items appear for a specific site, add a lookup_policy to site.cfg_json:
{
"lookup_policy": {
"country": ["US", "CA", "GB"],
"time_zone": ["America/New_York", "US/Eastern"]
}
}
Whitelist values must match the
groupfield — i.e., the natural key for that type (ISO code for country, IANA name for time zone). Using a display name will silently return no results for that item.
D. Adding and managing client overrides
When a client needs a customized label or wants to hide/reorder lookup items, create override records rather than modifying global defaults.
Rules:
- Never modify global default rows (
account_id = NULL). Those are shared across all accounts. Any change there affects every client. - Set
groupto the exact same value as the global default row for the item you are overriding. Ifgroupdoesn't match, the override creates a new item instead of replacing the existing one. - Set
account_idto the client's account ID. Leavefor_type/for_idnull unless the override is specific to a single object (e.g., one site).
Example — rename "US/Eastern" for one account:
INSERT INTO lu_v3_time_zone
(account_id, name, name_override, `group`, enable, priority, sort)
VALUES
(42, 'US/Eastern', 'Eastern Time (Client Label)', 'US/Eastern', 1, 1, 50);
The name_override field is the display label the frontend should prefer when set. group = 'US/Eastern' ensures this row competes with — and wins over — the global default in the PARTITION BY group deduplication.
To disable an item for one account (hide it from their dropdowns):
INSERT INTO lu_v3_time_zone
(account_id, name, `group`, enable)
VALUES
(42, 'US/Samoa', 'US/Samoa', 0);
Setting enable = 0 on an account-scoped row shadows the global default for that account only.
To remove a client override (revert to global default):
Simply delete the row where account_id = <client> and group = '<item>'. The global default row is unaffected and immediately resumes winning.
E. Adding new global lookup items
When seeding new lookup data (e.g., adding timezones in bulk):
- Set
group = namefor every row (fortime_zone). This is a hard invariant — ifgroupis set to a regional label like"United States"instead of the timezone name, the entire group collapses to a single winner and all but one entry disappear from the API response. - Set
account_id = NULLandfor_type = NULL/for_id = NULLfor global defaults. - After seeding, verify with:
-- Should return 0 rows; any result means multiple items will collapse into one SELECT `group`, COUNT(*) AS cnt FROM lu_v3_time_zone WHERE account_id IS NULL GROUP BY `group` HAVING cnt > 1;
5. 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
- Example:
Response Impact:
- 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)
- Nested Hosted File Object: A full
hosted_fileobject will be nested under thehosted_filekey. This object (Hosted_File_Basemodel) will contain all its standard fields, includingid(random string ID),hash_sha256,content_type,size, etc.
6. Hosted File Actions: Convert & Clip (Frontend Notes)
These helper endpoints let the frontend request small server-side transformations without uploading new blobs. They return a newly-created hosted_file metadata object on success.
-
Convert (PDF → Image)
- Method:
GET - Path:
/v3/action/hosted_file/{hosted_file_id}/convert_file - Required query params:
link_to_type,link_to_id - Optional query params:
filename_no_ext(defaults toautomated_hosted_file_conversion),to_type(defaults towebp) - Auth: standard V3 headers (
x-aether-api-key,x-account-id/x-no-account-id/?jwt=) - Behavior: converts the first page of a PDF to
webporpng, saves a newhosted_file, and returns its metadata. Returns 400 on failure.
- Method:
-
Clip Video
- Method:
GET - Path:
/v3/action/hosted_file/{hosted_file_id}/clip_video - Required query params:
link_to_type,link_to_id,start_time,end_time(formatHH:MM:SS) - Optional query params:
filename_no_ext(defaults toautomated_hosted_file_clip_video),reencode(bool),scale_down(bool) - Auth: standard V3 headers
- Behavior: extracts a clip using
ffmpegand saves it as a newhosted_file. Defaults to stream-copying to be fast; setreencode=trueto force H.264 orscale_down=trueto resize. Returns 400 on failure. - Behavior: extracts a clip using
ffmpegand saves it as a newhosted_file.- Defaults to stream-copying to be fast; set
reencode=trueto force H.264 orscale_down=trueto resize. - For longer-running clips you can schedule the job in the background by adding
?background=true. When scheduled the API returns202 Acceptedand the clip runs asynchronously on the server; check the returnedhosted_filerecord later via the standard V3hosted_fileendpoints. - Returns 400 on synchronous failure; returns 202 when scheduled successfully.
- Defaults to stream-copying to be fast; set
- Method:
Frontend guidance:
- Call these routes with the same
link_to_type/link_to_idyou plan to associate the resulting hosted_file with — the server resolves random IDs for you. - After a successful response, use the V3
hosted_fileaction endpoints (download/delete) to manage or retrieve the new file. - These endpoints run synchronously and can take time for large inputs; for heavy or batch workloads use a queued job pattern instead.
- These endpoints may take time for large inputs. Prefer using
?background=trueto schedule work and receive a202 Acceptedresponse for async processing. For heavy or batch workloads use a queued job pattern instead.
7. Event Exhibit Tracking Export (Leads Export)
Allows an exhibitor to download all lead-capture records for their exhibit as a CSV or XLSX file.
- Method:
GET - Path:
/v3/action/event_exhibit/{exhibit_id}/tracking_export - Auth: Standard V3 headers (
x-aether-api-key+x-account-idor?jwt=)
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
file_type |
CSV | XLSX |
CSV |
Output format. |
return_file |
bool | true |
true → file download response. false → JSON body with row data. |
Response
Content-Type: text/csv(CSV) orapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet(XLSX)Content-Disposition: attachment; filename="leads_export_<timestamp>.csv"- If there are no tracking records, a valid file with headers only is returned (not a 404).
Columns Returned
Fixed columns (always present), followed by any custom question columns flattened from responses_json:
event_exhibit_tracking_id, created_on, updated_on, event_exhibit_name, event_badge_full_name, event_badge_email, event_badge_professional_title, event_badge_affiliations, event_badge_location, event_badge_country, external_person_id, exhibitor_notes, priority, enable, hide, [custom question codes…]
Note:
exhibitor_noteshas HTML tags stripped automatically for clean CSV output.
Permission Requirement — leads_api_access
Important
This endpoint enforces a per-exhibit permission flag. The
event_exhibitrecord must haveleads_api_access = trueset in the database, OR the caller must have manager-level account access (JWT withmanager: true).If
leads_api_accessisfalseornullon the exhibit, the API returns:{ "detail": "Access denied: leads API access is not enabled for this exhibit." }Fix: Enable the flag on the exhibit record via
PATCH /v3/crud/event_exhibit/{id}with{ "leads_api_access": true }, or set it directly in the database/admin panel.
Dual purpose of leads_api_access
This flag serves two related but distinct roles:
- 3rd-party API access (original intent): Controls whether external systems (exhibitor apps, badge-scanning devices, etc.) are permitted to push or pull lead data for this exhibit via the API.
- UI export gate (new): The frontend should read
leads_api_accessfrom the exhibit record and use it to show or hide the export/download button. Only render the button when the flag istrue— this prevents users from triggering a request that will always 403.
The recommended pattern is to fetch the exhibit record first and gate the UI on this field before the user ever sees the export option. The API enforces the same check server-side as a safety net.
Example Request
const resp = await fetch(
`https://dev-api.oneskyit.com/v3/action/event_exhibit/${exhibitId}/tracking_export?file_type=CSV&return_file=true`,
{
headers: {
'x-aether-api-key': API_KEY,
'x-account-id': accountId,
},
}
);
// resp is a file blob — use URL.createObjectURL() or trigger a download
const blob = await resp.blob();
const url = URL.createObjectURL(blob);
8. Troubleshooting 403 Forbidden
If you receive a 403 on a valid ID:
- Verify
x-aether-api-keyis correct. - Ensure you are sending
x-account-idand NOTx-aether-api-token. - Verify the record actually belongs to the account ID you are sending.
- Check if the object is marked
public_read: Truein the registry. (Posts and Archive Content allow guest access; Journals and Badges do not).