openapi: 3.0.3 info: title: Aptabase Error Reporting API description: Error ingestion endpoint for the Aptabase error reporting feature. version: 0.1.0 servers: - url: https://localhost:3000 description: Local .NET backend (development) paths: /api/v0/error: post: operationId: postError summary: Ingest an application error description: | Accepts an error report from an application SDK and buffers it for storage in ClickHouse. The endpoint is CORS-enabled (AllowAny) and rate-limited to 20 requests/second per IP (`EventIngestion` policy). Returns **202 Accepted** on success (the error is buffered, not yet persisted). parameters: - name: App-Key in: header required: true description: | The application API key. Identifies which app the error belongs to. The key is normalised to uppercase on the server side. schema: type: string example: "A-1234567890" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ErrorBody" example: errorMessage: "NullReferenceException: Object reference not set to an instance of an object" errorType: "NullReferenceException" stackTrace: "at MyApp.Services.UserService.GetUser(Int32 id) in /src/Services/UserService.cs:line 42" timestamp: "2026-02-17T12:00:00Z" platform: "Android" osName: "Android" osVersion: "14" appVersion: "1.2.3" sdkVersion: "aptabase-flutter@0.1.0" sessionId: "abc-123-def" severity: "fatal" kind: "crash" isDebug: false responses: "202": description: Accepted – error has been buffered for processing. "400": description: Bad request – invalid payload or missing App-Key header. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: invalidPayload: value: error: "Invalid error payload" missingAppKey: value: error: "Missing App-Key header" lockedAccount: value: error: "Owner account is locked" "401": description: Unauthorized – the App-Key does not match any registered app. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: "Invalid App-Key" "403": description: | Forbidden – the app has exhausted its monthly error quota. Clients should NOT retry: the quota only resets on the 1st of each month, so the report should be dropped. (403 is used deliberately instead of 429, because SDKs treat 429 as retryable.) content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: "Error quota exceeded" "429": description: | Too Many Requests – returned ONLY by the per-IP rate limiter (20 req/s per IP, `EventIngestion` policy). Retrying after a short delay is appropriate. Quota exhaustion is reported as 403, never 429. options: operationId: optionsError summary: CORS preflight for error ingestion description: Returns 200 to satisfy browser CORS preflight checks. responses: "200": description: OK /api/v0/apps/{appId}/errors/types: get: operationId: getErrorTypes summary: List top error types for an app description: | Returns the distinct error types recorded for an app within a date range, together with how many times each occurred, ordered by occurrence count (top 50). Used by the dashboard to populate the error-type filter dropdown. This is a dashboard endpoint: it requires an authenticated dashboard session (cookie) and is rate-limited by the `Stats` policy (1000 requests/hour per IP). Access is always checked against the raw app id, before any build-mode suffix is applied. parameters: - name: appId in: path required: true description: The app id (raw, without any build-mode suffix). schema: type: string - name: buildMode in: query required: false description: | Which build of the app to query. `debug` reads errors stored under the `_DEBUG`-suffixed app id; anything else (or omitted) reads release errors. schema: type: string enum: [release, debug] - name: startDate in: query required: false description: | Start of the date range (ISO 8601). Defaults to 7 days before `endDate` when omitted. schema: type: string format: date-time - name: endDate in: query required: false description: | End of the date range (ISO 8601). Defaults to the current time when omitted. schema: type: string format: date-time responses: "200": description: The list of error types with occurrence counts. content: application/json: schema: type: array items: $ref: "#/components/schemas/ErrorTypeItem" example: - name: "InvalidOperationException" value: 42 - name: "NullReferenceException" value: 17 "403": description: Forbidden – the user does not have read access to the app. components: schemas: ErrorBody: type: object required: - errorMessage - errorType properties: errorMessage: type: string maxLength: 5000 description: Human-readable error message. errorType: type: string maxLength: 100 description: Error class/type name (e.g. "NullReferenceException", "TypeError"). stackTrace: type: string maxLength: 10000 description: Full stack trace. Optional. timestamp: type: string format: date-time description: | When the error occurred (ISO 8601 / UTC). If omitted or in the future, the server clamps it to the current time. platform: type: string maxLength: 30 description: Platform identifier (e.g. "Android", "iOS", "Windows", "Web"). osName: type: string maxLength: 30 description: Operating system name. osVersion: type: string maxLength: 100 description: Operating system version. appVersion: type: string maxLength: 50 description: Application version string. sdkVersion: type: string maxLength: 40 description: SDK identifier and version (e.g. "aptabase-flutter@0.1.0"). sessionId: type: string maxLength: 100 description: Client session identifier. severity: type: string maxLength: 20 enum: [fatal, error] description: | Error severity. Matched case-insensitively against the known values; unknown values are stored as an empty string (the report itself is never rejected over an unknown severity). kind: type: string maxLength: 20 enum: [crash, unhandled, taskException, handled] description: | How the error was captured. Matched case-insensitively against the known values; unknown values are stored as an empty string (the report itself is never rejected over an unknown kind). isDebug: type: boolean default: false description: | Whether the error originated from a debug build of the app. Errors from debug builds are stored separately from release builds (under a `_DEBUG`-suffixed app id) so they never mix in the dashboard. Older SDKs that omit this field are treated as release builds. ErrorResponse: type: object properties: error: type: string description: Human-readable error message. ErrorTypeItem: type: object properties: name: type: string description: The error type/class name (e.g. "InvalidOperationException"). value: type: integer description: Number of error events with this type within the date range.