> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aiola.app/llms.txt
> Use this file to discover all available pages before exploring further.

# API Reference

> Send feedback, application logs, browser analytics, and crawler telemetry to Aiola over HTTP.

Use these public endpoints to send project data without the desktop app. Feedback and App Logs use the secret project key from **Settings → API Key**; analytics endpoints use the site's public tracking ID and configured domain.

## Submit feedback

```text theme={null}
POST https://lhrsgoaselmgophvbqme.supabase.co/functions/v1/submit-feedback
Authorization: Bearer <project-key>
Content-Type: application/json
```

<ParamField path="type" type="string" required>
  `issue` or `feature_request`. Any other value is treated as `issue`.
</ParamField>

<ParamField path="<configured-field-key>" type="string | number | boolean | string[]">
  A field configured in **Settings → Feedback**. Required fields must be present. Email and URL fields are validated; attachment fields accept image data URIs.
</ParamField>

<ParamField path="<extra-field-key>" type="string | number | boolean">
  Up to 20 scalar fields not in the saved configuration. Extra strings are stripped of HTML and truncated to 500 characters.
</ParamField>

```bash theme={null}
curl -X POST 'https://lhrsgoaselmgophvbqme.supabase.co/functions/v1/submit-feedback' \
  -H 'Authorization: Bearer aio_REPLACE_WITH_PROJECT_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "issue",
    "title": "Checkout fails",
    "email": "you@example.com",
    "device": "macOS - Intel",
    "description": "The payment form returns an error."
  }'
```

Success is HTTP `200`:

```json theme={null}
{ "success": true }
```

| Status | Trigger                                                                                                                                                                     |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid JSON, a configured required field is absent, or a configured email or URL is invalid. Validation returns `{ "error": "Invalid request", "fields": ["field_key"] }`. |
| `401`  | The bearer token is missing or does not match a project key.                                                                                                                |
| `402`  | The key owner has neither a Pro subscription nor an active trial.                                                                                                           |
| `405`  | The method is not `POST` or `OPTIONS`.                                                                                                                                      |
| `413`  | The JSON text exceeds 5 MiB (`5 * 1024 * 1024` characters).                                                                                                                 |
| `429`  | More than 20 submissions per 60 seconds come from one source IP, or the key's `rate_limit_per_minute` is exceeded.                                                          |
| `500`  | The feedback row cannot be inserted.                                                                                                                                        |

Configured `text` fields are truncated to 500 characters and `long_text` fields to 10,000. A configured attachment field accepts up to 5 PNG, JPEG, or WebP `data:image/...;base64,...` values; an image whose base64 content exceeds `5 * 1024 * 1024 * 1.4` characters is skipped. Unknown arrays and objects are not stored.

## Ingest App Logs

```text theme={null}
POST https://aiola.app/api/app-logs-ingest
x-aiola-key: <project-key>
Content-Type: application/json
```

`x-aiola-key` is preferred. `Authorization: Bearer <project-key>` is also accepted. See [App Logs over HTTP](/reference/app-logs-http) for complete runtime examples.

<ParamField path="message" type="string">
  Human-readable event message. At least `message` or `exception` is required.
</ParamField>

<ParamField path="level" type="string">
  The dashboard understands `fatal`, `error`, `warning`, `info`, and `debug`. The endpoint accepts and stores any string; an absent or empty value defaults to `error`.
</ParamField>

<ParamField path="exception" type="object">
  Exception `type`, `value`, and optional `stacktrace.frames`.
</ParamField>

<ParamField path="tags" type="object">
  String key/value metadata. `environment` and `release` tags are fallbacks for their top-level fields. A numeric `status_code` tag is clamped to `0`–`65535`.
</ParamField>

<ParamField path="breadcrumbs" type="array">
  Prior events with `timestamp`, `category`, `message`, optional `level`, and optional `data`.
</ParamField>

<ParamField path="request" type="object">
  Optional `url`, `method`, `headers`, `query_string`, and `body`.
</ParamField>

<ParamField path="environment" type="string" />

<ParamField path="release" type="string" />

<ParamField path="platform" type="string" />

<ParamField path="timestamp" type="string">
  An ISO 8601 timestamp. Invalid or absent values use the current time.
</ParamField>

<ParamField path="user" type="object">
  Optional `id`, `email`, `username`, and `ip_address`.
</ParamField>

<ParamField path="contexts" type="object">
  Optional `browser`, `os`, `device`, and `runtime` objects.
</ParamField>

```bash theme={null}
curl -X POST 'https://aiola.app/api/app-logs-ingest' \
  -H 'x-aiola-key: aio_REPLACE_WITH_PROJECT_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"message":"Checkout failed","level":"error","environment":"production"}'
```

Success is HTTP `200`:

```json theme={null}
{ "ok": true, "group_hash": "<32-character-hash>", "is_new": true }
```

| Status | Trigger                                                                                                                                                              |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid JSON, or neither `message` nor `exception` is present.                                                                                                       |
| `401`  | The project key is missing or invalid.                                                                                                                               |
| `402`  | The key owner has neither a Pro subscription nor an active trial.                                                                                                    |
| `403`  | App Logs is disabled for the project.                                                                                                                                |
| `405`  | The method is not `POST` or `OPTIONS`.                                                                                                                               |
| `413`  | Request text exceeds 512,000 characters.                                                                                                                             |
| `429`  | The key's `logs_rate_limit_per_minute` is exceeded; the fallback is 60 events per 60 seconds. A second backstop allows 120 events per 60 seconds from one source IP. |
| `500`  | Fingerprint creation, event storage, or issue-group update fails.                                                                                                    |
| `504`  | The public gateway times out while contacting the ingest function.                                                                                                   |

Messages and exception values are truncated after 8,192 characters, stack frames after 200, breadcrumbs to the newest 100, and tags to the first 50. Tag values, retained request-header values, and user ID/email/username are capped at 500 characters; request bodies at 4,096. The ingest removes `authorization`, `cookie`, `set-cookie`, `x-aiola-key`, `x-api-key`, `x-auth-token`, and `proxy-authorization` request headers.

## Collect browser analytics

```text theme={null}
POST https://aiola.app/api/collect
Content-Type: text/plain
Authentication: none
```

<ParamField path="tracking_id" type="string" required>
  The site's public analytics tracking ID.
</ParamField>

<ParamField path="event_id" type="string">
  An 8–64 character hexadecimal/hyphen ID. The collector creates a UUID if it is absent or invalid.
</ParamField>

<ParamField path="seq" type="number">
  Per-page event sequence, clamped to `0`–`4294967295`.
</ParamField>

<ParamField path="v" type="string">
  Tracker version. The current browser tracker sends `2`.
</ParamField>

<ParamField path="event_name" type="string">
  Empty for a page view; `_leave`, `_identify`, and custom names have special handling.
</ParamField>

<ParamField path="event_data" type="string">
  JSON serialized as a string.
</ParamField>

<ParamField path="url_path" type="string" />

<ParamField path="url_query" type="string" />

<ParamField path="url_host" type="string">
  When present, must equal the configured domain or one of its subdomains. Omitting it bypasses host authorization and loses the event's host value, so the collector cannot distinguish production, preview, and other hosts.
</ParamField>

<ParamField path="referrer" type="string" />

<ParamField path="title" type="string" />

<ParamField path="language" type="string" />

<ParamField path="screen" type="string" />

<ParamField path="distinct_id" type="string" />

<ParamField path="visitor_id" type="string" />

<ParamField path="session_id" type="string" />

<ParamField path="duration" type="number">
  Seconds, clamped to `0`–`3600`.
</ParamField>

```bash theme={null}
curl -X POST 'https://aiola.app/api/collect' \
  -H 'Content-Type: text/plain' \
  --data-raw '{
    "tracking_id":"site_REPLACE_ME",
    "event_id":"6f56d31e-178d-4a94-89a6-f6ce4f362d55",
    "seq":1,
    "v":"2",
    "event_name":"",
    "event_data":"{}",
    "url_path":"/pricing",
    "url_query":"?utm_source=docs",
    "url_host":"example.com",
    "referrer":"https://docs.example.com/",
    "title":"Pricing",
    "language":"en-US",
    "screen":"1440x900",
    "distinct_id":"",
    "visitor_id":"visitor_123",
    "session_id":"session_123",
    "duration":0
  }'
```

Success is HTTP `204` with no response body. Excluded traffic and recognized bot traffic also return `204`; a `204` therefore means accepted or intentionally ignored, not necessarily stored as a human event.

| Status | Trigger                                                                                                                                                        |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid JSON or missing `tracking_id`.                                                                                                                         |
| `402`  | The site owner has neither a Pro subscription nor an active trial.                                                                                             |
| `403`  | The site has no configured domain, `url_host` is outside its domain, or Attack Mode rejects a short/tool/headless user agent or a request without a client IP. |
| `404`  | The tracking ID is unknown or the analytics site is inactive.                                                                                                  |
| `405`  | The method is not `POST` or `OPTIONS`.                                                                                                                         |
| `413`  | Request text exceeds 10,000 characters.                                                                                                                        |
| `429`  | Normal-mode backstop: over 30 events/minute per IP or 6,000/minute per site. Attack Mode adds limits of 3/minute per IP and 600/minute per site.               |
| `500`  | The public gateway fails while contacting the collector.                                                                                                       |
| `504`  | The public gateway request times out.                                                                                                                          |

The browser tracker sends at most 30 events per 10 seconds. It truncates event names to 100 characters and replaces `event_data` with `{}` when its serialized length exceeds 6,000 characters. The collector caps paths and queries at 2,000 characters, host and title at 500, identity at 200, and screen/language at 20; collector-side `event_name` storage is capped at 200 and `event_data` is replaced with `{}` beyond 8,000.

## Ingest bot and crawler analytics

```text theme={null}
POST https://aiola.app/api/bot-crawl-ingest
Content-Type: application/json
Authentication: none
```

<ParamField path="tracking_id" type="string" required>
  The site's public analytics tracking ID.
</ParamField>

<ParamField path="url_path" type="string">
  Requested path; defaults to `/` and is capped at 2,000 characters.
</ParamField>

<ParamField path="url_host" type="string">
  When present, must equal the configured domain or one of its subdomains; capped at 500 characters. Omitting it bypasses host authorization and stores no host, so crawler telemetry cannot be attributed or filtered by hostname.
</ParamField>

<ParamField path="ua" type="string" required>
  The crawler's original User-Agent. It is capped at 1,000 characters and must match a crawler in [AI Crawlers](/reference/bot-crawlers).
</ParamField>

<ParamField path="country" type="string">
  Optional two-letter uppercase country code. `T1`, `XX`, and invalid values are stored as empty.
</ParamField>

<ParamField path="status" type="number">
  HTTP status returned to the crawler. Defaults to `200`; values outside `100`–`599` become `200`.
</ParamField>

```bash theme={null}
curl -X POST 'https://aiola.app/api/bot-crawl-ingest' \
  -H 'Content-Type: application/json' \
  -d '{
    "tracking_id":"site_REPLACE_ME",
    "url_path":"/docs/api",
    "url_host":"example.com",
    "ua":"GPTBot/1.2",
    "country":"US",
    "status":200
  }'
```

Success is HTTP `204` with no response body. Unknown crawler User-Agents are intentionally ignored with the same `204` response.

| Status | Trigger                                                                       |
| ------ | ----------------------------------------------------------------------------- |
| `400`  | Invalid JSON or missing `tracking_id`.                                        |
| `402`  | The site owner has neither a Pro subscription nor an active trial.            |
| `403`  | The site has no configured domain or `url_host` is outside its domain.        |
| `404`  | The tracking ID is unknown or the analytics site is inactive.                 |
| `405`  | The method is not `POST` or `OPTIONS`.                                        |
| `413`  | Request text exceeds 10,000 characters.                                       |
| `429`  | More than 6,000 events/minute for the tracking ID reach one function isolate. |
| `500`  | The public gateway fails while contacting the ingest function.                |
| `504`  | The public gateway request times out.                                         |

## Owner-side endpoints

These endpoints belong to the signed-in desktop application. They require an Aiola user session and subscription access through the app's authenticated request client; a project key is not accepted.

| Operation                | Method and path                                                       | Input                                                                                   | Confirmed result or limit                                                                                               |
| ------------------------ | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| List feedback            | `GET /api/feedback?project_name=<name>&type=<issue\|feature_request>` | `project_name` is required; `type` is optional                                          | Newest first; maximum 1,000 rows                                                                                        |
| Update feedback status   | `PATCH /api/feedback?id=<id>` or `PATCH /api/feedback`                | `{ "status": "open\|in_progress\|human_review\|closed\|ignored", "ids": ["..."] }`      | Maximum 500 IDs                                                                                                         |
| Delete feedback          | `DELETE /api/feedback?id=<id>` or `DELETE /api/feedback`              | `{ "ids": ["..."] }`                                                                    | Maximum 500 IDs; stored attachments are deleted best-effort                                                             |
| Read project key         | `GET /api/feedback-key?project_name=<name>`                           | Query parameter                                                                         | Returns the key hint and configuration, never the raw key                                                               |
| Create project key       | `POST /api/feedback-key`                                              | `{ "project_name": "..." }`                                                             | One key per user/project; returns `raw_api_key` once and creates default feedback schemas                               |
| Update key configuration | `PATCH /api/feedback-key`                                             | `{ "project_name": "...", "issue_field_config": [...], "feature_field_config": [...] }` | At least one configuration field is required                                                                            |
| Revoke project key       | `DELETE /api/feedback-key?project_name=<name>`                        | Query parameter                                                                         | Deletes the key                                                                                                         |
| Regenerate project key   | `POST /api/feedback-key/regenerate`                                   | `{ "project_name": "..." }`                                                             | Returns `raw_api_key` once; preserves both schemas and `rate_limit_per_minute`; 5 requests per shared rate-limit window |
| Scan Setup Health        | `GET /api/workspace/setup-health?workspaceId=<id>&force=1`            | `workspaceId`; optional `force=1`                                                       | Local non-SSH workspace; Pro or active trial; cached for 1 hour unless forced                                           |
| Update logger            | `POST /api/workspace/setup-health/update-logger`                      | `{ "workspaceId": "...", "packageDir": "." }`                                           | Directory must resolve inside the workspace and its `package.json` must list `@aiolaapp/logger`                         |

See [Setup Health](/core-features/setup-health) for scan results and remediation behavior.

## Limits at a glance

| Endpoint              | Body or field limits                                                                                                                            | Rate limits                                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Feedback              | 5 MiB JSON text; text 500; long text 10,000; 20 extra scalar keys; 5 attachment images; approximately 5 MiB binary per image                    | 20/minute/source IP plus project `rate_limit_per_minute`                                                      |
| App Logs              | body 512,000; message/value 8,192; breadcrumbs 100; frames 200; tags 50; tag/header/user value 500; request body 4,096; status code `0`–`65535` | Project setting; fallback 60/minute/key; 120/minute/source IP                                                 |
| Browser analytics     | body 10,000; event name 100 in tracker; event data 6,000 in tracker; path/query 2,000; host/title 500                                           | Tracker 30/10 seconds; normal 30/minute/IP and 6,000/minute/site; Attack Mode 3/minute/IP and 600/minute/site |
| Bot/crawler analytics | body 10,000; path 2,000; host 500; UA 1,000; status `100`–`599`                                                                                 | 6,000/minute/site/function isolate                                                                            |
| Feedback owner APIs   | list 1,000 rows; mutation 500 IDs                                                                                                               | Session access rules apply                                                                                    |
| Setup Health          | 6,000 scanned files; 512 KiB/file; 48 MiB total; directory walk depth 8                                                                         | Workspace scan and npm-latest result cached 1 hour; logger update timeout 300,000 ms                          |
