Request contract
POST https://aiola.app/api/app-logs-ingest
x-aiola-key: <project-key>
Content-Type: application/json
string
Required unless
exception is present; maximum stored length is 8,192 characters.string
fatal, error, warning, info, or debug. Defaults to error.object
type, value, and optional stacktrace.frames; at most 200 frames.object
At most 50 string values, each capped at 500 characters. A numeric
status_code tag is clamped to 0–65535.array
The newest 100 entries are retained. Each entry can contain
timestamp, category, message, level, and data.string
string
string
string
object
object
object
aio_REPLACE_WITH_PROJECT_KEY or set AIOLA_KEY as shown.
- curl
- Swift
- Kotlin
- Go
- Rust
- Python
- Node
- React Native
- Shell trap
curl -X POST 'https://aiola.app/api/app-logs-ingest' \
-H "x-aiola-key: ${AIOLA_KEY}" \
-H 'Content-Type: application/json' \
-d '{"message":"Checkout failed","level":"error","platform":"http","environment":"production","release":"1.4.2","tags":{"app":"store","screen":"checkout"},"breadcrumbs":[{"timestamp":"2026-09-19T12:00:00Z","category":"navigation","message":"Opened checkout","level":"info"}]}'
import Foundation
var request = URLRequest(url: URL(string: "https://aiola.app/api/app-logs-ingest")!)
request.httpMethod = "POST"
request.setValue("aio_REPLACE_WITH_PROJECT_KEY", forHTTPHeaderField: "x-aiola-key")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"message": "Checkout failed", "level": "error", "platform": "http",
"environment": "production", "release": "1.4.2",
"tags": ["app": "store", "screen": "checkout"],
"breadcrumbs": [["timestamp": "2026-09-19T12:00:00Z", "category": "navigation", "message": "Opened checkout", "level": "info"]]
])
let (_, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw URLError(.badServerResponse) }
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
fun main() {
val body = """{"message":"Checkout failed","level":"error","platform":"http","environment":"production","release":"1.4.2","tags":{"app":"store","screen":"checkout"},"breadcrumbs":[{"timestamp":"2026-09-19T12:00:00Z","category":"navigation","message":"Opened checkout","level":"info"}]}"""
val request = HttpRequest.newBuilder(URI("https://aiola.app/api/app-logs-ingest"))
.header("x-aiola-key", "aio_REPLACE_WITH_PROJECT_KEY")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build()
val response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString())
require(response.statusCode() == 200) { response.body() }
}
package main
import (
"bytes"
"log"
"net/http"
)
func main() {
payload := []byte(`{"message":"Checkout failed","level":"error","platform":"http","environment":"production","release":"1.4.2","tags":{"app":"store","screen":"checkout"},"breadcrumbs":[{"timestamp":"2026-09-19T12:00:00Z","category":"navigation","message":"Opened checkout","level":"info"}]}`)
req, err := http.NewRequest("POST", "https://aiola.app/api/app-logs-ingest", bytes.NewReader(payload))
if err != nil { log.Fatal(err) }
req.Header.Set("x-aiola-key", "aio_REPLACE_WITH_PROJECT_KEY")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer res.Body.Close()
if res.StatusCode != http.StatusOK { log.Fatalf("Aiola returned %s", res.Status) }
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
reqwest::Client::new()
.post("https://aiola.app/api/app-logs-ingest")
.header("x-aiola-key", "aio_REPLACE_WITH_PROJECT_KEY")
.json(&serde_json::json!({
"message": "Checkout failed", "level": "error", "platform": "http",
"environment": "production", "release": "1.4.2",
"tags": {"app": "store", "screen": "checkout"},
"breadcrumbs": [{"timestamp": "2026-09-19T12:00:00Z", "category": "navigation", "message": "Opened checkout", "level": "info"}]
}))
.send().await?.error_for_status()?;
Ok(())
}
import os
import requests
response = requests.post(
"https://aiola.app/api/app-logs-ingest",
headers={"x-aiola-key": os.environ["AIOLA_KEY"]},
json={
"message": "Checkout failed", "level": "error", "platform": "http",
"environment": "production", "release": "1.4.2",
"tags": {"app": "store", "screen": "checkout"},
"breadcrumbs": [{"timestamp": "2026-09-19T12:00:00Z", "category": "navigation", "message": "Opened checkout", "level": "info"}],
},
timeout=10,
)
response.raise_for_status()
const response = await fetch("https://aiola.app/api/app-logs-ingest", {
method: "POST",
headers: { "x-aiola-key": process.env.AIOLA_KEY, "content-type": "application/json" },
body: JSON.stringify({
message: "Checkout failed", level: "error", platform: "http",
environment: "production", release: "1.4.2",
tags: { app: "store", screen: "checkout" },
breadcrumbs: [{ timestamp: "2026-09-19T12:00:00Z", category: "navigation", message: "Opened checkout", level: "info" }],
}),
});
if (!response.ok) throw new Error(`Aiola returned ${response.status}: ${await response.text()}`);
async function sendLog() {
const response = await fetch("https://aiola.app/api/app-logs-ingest", {
method: "POST",
headers: { "x-aiola-key": "aio_REPLACE_WITH_PROJECT_KEY", "content-type": "application/json" },
body: JSON.stringify({
message: "Checkout failed", level: "error", platform: "http",
environment: "production", release: "1.4.2",
tags: { app: "store", screen: "checkout" },
breadcrumbs: [{ timestamp: "2026-09-19T12:00:00Z", category: "navigation", message: "Opened checkout", level: "info" }],
}),
});
if (!response.ok) throw new Error(`Aiola returned ${response.status}`);
}
void sendLog();
set -Eeuo pipefail
report_failure() {
local exit_code="$?"
local command="${BASH_COMMAND}"
trap - ERR
curl --silent --show-error --max-time 10 \
-X POST 'https://aiola.app/api/app-logs-ingest' \
-H "x-aiola-key: ${AIOLA_KEY}" \
-H 'Content-Type: application/json' \
--data "$(jq -n --arg message "Command failed: ${command}" '{message:$message,level:"error",platform:"http",environment:"production",release:"1.4.2",tags:{app:"store",screen:"checkout"},breadcrumbs:[{timestamp:"2026-09-19T12:00:00Z",category:"navigation",message:"Opened checkout",level:"info"}]}')" \
>/dev/null || true
exit "$exit_code"
}
trap report_failure ERR
Send an exception
Frames are stored in the order sent. Includein_app: true on application frames; Aiola selects the last in-app frame as the issue culprit.
{
"message": "Checkout failed",
"level": "error",
"exception": {
"type": "PaymentError",
"value": "Card declined",
"stacktrace": {
"frames": [
{
"filename": "CheckoutView.swift",
"function": "submitOrder",
"lineno": 128,
"colno": 17,
"context_line": "try await submit(order)",
"in_app": true
}
]
}
},
"platform": "ios",
"environment": "production",
"release": "1.4.2"
}
Tags and breadcrumbs
| HTTP field | App Logs behavior |
|---|---|
tags | Stored as searchable event metadata. Top-level environment and release win; otherwise tags.environment and tags.release supply them. tags.sdk_name and tags.sdk_version populate SDK metadata; platform is the fallback SDK name. |
breadcrumbs | Stored as the ordered trail before the failure. If more than 100 are sent, Aiola keeps the newest 100. |
request.headers | Stored after secret-bearing headers are removed; retained values are capped at 500 characters. |
request.body | Stored up to 4,096 characters, then marked as truncated. |
Response and errors
{ "ok": true, "group_hash": "<32-character-hash>", "is_new": true }
| Status | Meaning |
|---|---|
200 | Stored; group_hash identifies the issue group and is_new tells whether the group was created. |
400 | Invalid JSON, or neither message nor exception was sent. |
401 | Missing or invalid project key. |
402 | The key owner has neither a Pro subscription nor an active trial. |
403 | App Logs is disabled for this project. |
405 | The method is not POST or OPTIONS. |
413 | Request text exceeds 512,000 characters. |
429 | The project limit was exceeded; the fallback is 60 events/minute per key. The source-IP backstop is 120 events/minute. |
500 | Aiola could not fingerprint, store, or group the event. |
504 | The public gateway timed out. |

