S
supero.docs
Developers/Platform Overview/Operations & Observability

Operations & Observability

What Supero records, where each kind of record is read from, how to follow one request across the whole platform, what to check when something fails — and, stated plainly, which observability surfaces do not exist so you can plan to build them yourself.

Overview

Supero records a lot about what happens on it. What it does not do is present that as a monitoring product: there is no dashboard to open, no metric to graph, no alert to configure. What exists is a set of queryable records — audit entries, workflow runs, connector runs, deployment status, a recent-activity log — and one identifier that ties a single request together across them.
This page assembles those surfaces in one place. Nothing here is new capability; it is scattered across half a dozen pages and, in the case of the log query API, documented nowhere else. If you are deciding what to build on top before going to production, read the what is not provided section first — that is the part that determines how much work you have.

ℹ️ The shape of the answer

Records over streams. Everything is a thing you query when you go looking, not a thing that arrives when something goes wrong. Nothing pushes to you. If you need to know that a nightly connector failed at 03:00, something you write has to ask.

What Is Recorded, and Where You Read It

SurfaceHow you read itHow far back
Audit trail — auth attempts, admin actions, schema changes, CRUD operationsThe audit_log object type, through the ordinary read and list endpoints90 days by default
Recent platform activity — request, response, auth, session, database and system log linesGET /api/v1/logsA rolling recent window. Not an archive — see below.
One request across servicesGET /api/v1/logs/trace/{correlation_id}Same rolling window as above
Workflow runs and per-step detailThe wf:workflow_instance and wf:workflow_step_log object types, through CRUDAs long as you keep the records
Connector runs, stats and recent executionsGET /api/v1/connectors/{connector_id}/status, and the Run History tabThe last 50 executions in the console
Deployment progress and outcomeThe poll URL returned by a launch or publish callCurrent state per lane
Container logs for a published appThe Cloud Deploy Logs page in the dashboardUp to 24 hours back, 500 lines per request
AI call outcomesThe correlation_id on the response envelopeOnly as long as you keep it
Read that table as two groups. The middle rows — workflow and connector runs — are ordinary records in your own data, so they persist exactly as long as you leave them there and you can query, filter and aggregate them like anything else. The log surfaces are bounded windows: they answer "what just happened", not "what happened last quarter".

Tracing One Request End to End

The correlation id is the single thread that runs through everything on this page. Get comfortable with it before anything else, because it turns "something failed somewhere" into one query.

Set your own, or read the one you were given

Send an X-Correlation-ID header — or an X-Request-Id, which is accepted the same way and read first — and Supero adopts your value instead of minting its own. Send nothing and one is generated for you and written to the logs. Getting it back, though, is narrower than it sounds: the id is echoed in the response body only on a 500, and only when you set X-Correlation-ID specifically — a request that sent only X-Request-Id gets a scrubbed 500 body without it, and 2xx and 4xx responses never carry it. So the dependable way to have an id to quote is to set your own and record it. When the id is present, a scrubbed 500 body looks like:
json
{
  "error": "Internal server error",
  "correlation_id": "a1b2c3d4"
}
That is by design: no internal detail escapes, and the identifier is the useful part. Errors & Status Codes covers the error envelope in full.

⚠️ Two limits on adopting your own value

The value you send is sanitized before it is adopted: it is capped in length and restricted to letters, digits and a few separators. A value that does not pass — one carrying whitespace or a newline, for instance — is silently replaced with a generated id rather than rejected with a 400, so your identifier and Supero’s will not match and nothing will tell you. Send a plain token, and read the correlation_id back from the response rather than assuming the one you sent was used. Separately, the AI endpoints do not adopt a supplied value at all — the correlation_id on an AI response is always one Supero minted, so read it from the envelope. See AI Agent API.

💡 Set it from your own request id

If your application already has a request id, send it as X-Correlation-ID rather than letting Supero mint an unrelated one. Then a single value joins your logs to Supero's, and a support conversation starts from an identifier you can find on both sides. Log it on every non-2xx response even when you are going to retry — a correlation id you did not keep is a trace you cannot pull.

Pull the trace

bash
curl "https://api.supero.dev/api/v1/logs/trace/a1b2c3d4" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
This returns every log entry recorded against that correlation id, up to 500 entries, ordered newest first — which is usually the whole life of the request. Entries outside your own domain are filtered out before you see them.

Querying Platform Logs

GET /api/v1/logs is a filtered read over recent platform activity for your domain. It is the closest thing to a log search that exists, and it is not documented anywhere else, so a few properties are worth stating precisely.
bash
# The last 200 errors and warnings in your domain
curl "https://api.supero.dev/api/v1/logs?levels=error,warning&limit=200" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Only authentication activity
curl "https://api.supero.dev/api/v1/logs?categories=auth,session" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Everything since a point you already reached
curl "https://api.supero.dev/api/v1/logs?since_seq=48211&limit=500" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
ParameterAccepts
levelsComma-separated: debug, info, warning, error
categoriesComma-separated: request, response, operation, auth, session, db, system
correlation_idA single correlation id — the same filter the trace endpoint applies
servicesComma-separated service names. The trace store aggregates three — platform-core, api-server and ai-service — so a name outside that set (a connector or tenant-service line, say) matches nothing, and a trace can stop at that boundary.
tenant_name / project_nameNarrow within your domain
since_seqA sequence number from a previous response — returns only what is newer
since_timestampAn ISO-8601 timestamp
limitNumber of entries to return. Defaults to 100 and is capped at 500 — ask for more and you get 500.
The response is an object, not a bare array:
json
{
  "logs": [
    {
      "seq": 48214,
      "timestamp": "2026-08-26T09:14:07Z",
      "level": "error",
      "category": "operation",
      "service": "...",
      "message": "...",
      "correlation_id": "a1b2c3d4",
      "domain_name": "acme-corp",
      "project_name": "storefront",
      "tenant_name": null
    }
  ],
  "last_seq": 48214,
  "total_in_buffer": 9873
}
Keep last_seq from each response and pass it back as since_seq on the next call. That is how you poll without re-reading what you already have, and it is stable in a way that a timestamp is not.

⚠️ This is a recent-activity window, not a log archive

Entries are held in a bounded rolling buffer and evicted oldest-first as new ones arrive — total_in_buffer in the response tells you how many are currently held. A busy domain can push an entry out of the window in minutes. Nothing here is a retention mechanism: if a log line matters beyond the moment, your own system has to pull it and keep it. Do not design an audit or compliance answer on top of this endpoint; that is what the audit trail is for.
  • •Authentication is a user JWT only. This endpoint does not accept an API key, so a background job that authenticates with X-API-Key cannot read it — log in and use the access token instead.
  • •Scope is your domain, enforced server-side. Asking for another domain returns 403; asking for nothing returns your own domain. Platform-level operator roles see more; every role you assign sees your domain only. See Roles & Permissions Reference.
  • •It is rate limited per domain and returns 429 with a retry_after value in the body when you exceed it. Poll on an interval, not in a loop.
  • •The statistics endpoint at GET /api/v1/logs/stats is restricted to platform operator roles and returns 403 for everyone else. It is not part of your surface.
  • •These routes are not available on every deployment. Where the backing store is not configured, they are not registered at all and return 404 rather than an empty result. Confirm the endpoint answers on your target environment before you build a triage tool on it — particularly on a self-hosted install.

The Audit Trail

Where the log window answers "what just happened", the audit trail answers "who did this, and when". It is recorded per domain as ordinary records of the audit_log object type, so you read it through the standard read and list endpoints with the same filtering and pagination as your own data.
bash
curl "https://api.supero.dev/api/v1/crud/acme-corp/audit_log?limit=100" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Entries carry the event type — authentication success and failure, administrative actions, data access, schema operations, CRUD operations, API requests and rate-limit events — plus a severity, a UTC timestamp, the actor, the kind and identity of the thing touched, the action, the client address and user agent, the request method, path and response status, and a details field. Security covers the fields you will use most.

⚠️ An audit read can answer 200 with nothing rather than 403

Audit reads are one of the surfaces that respond to an unauthorised request with 200 and an empty list instead of a denial. An empty audit page therefore means either nothing happened or you are not allowed to see it, and the response does not distinguish them. Before you conclude a period was quiet, confirm with a credential you know has the scope — see Errors & Status Codes.

⚠️ The audit trail is not a backup

It records that a delete happened — actor, time, which record — and not what the record contained. Since the CRUD API has no soft delete, no trash and no restore, an audit entry tells you exactly what you lost and cannot give it back. Default retention is 90 days, so an entry also stops being available before most annual reviews happen. If you need either property, export it on a schedule the same way you export records — see Continuity & Exit.

Workflow Runs

Every workflow execution writes a run record and one record per step, both queryable as ordinary data. This is the best-instrumented surface on the platform: you get the run-level summary, the per-step input and output, durations, retry counts, and — where compensation ran — which steps were rolled back and in what order.
bash
# Every failed run
curl -X POST https://api.supero.dev/api/v1/crud/acme-corp/query \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "wf:workflow_instance",
    "filters": { "status": "failed" },
    "limit": 100
  }'

# The step-by-step trail for one run
curl -X POST https://api.supero.dev/api/v1/crud/acme-corp/query \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "wf:workflow_step_log",
    "filters": { "instance_uuid": "9c1e2a30-..." }
  }'

⚠️ Filter with POST /query, not a query string

A filter appended to a GET list URL is not applied — unrecognised query parameters are dropped rather than rejected, so the call returns 200 with an unfiltered page and looks like it worked. That is exactly how a monitoring job comes back saying nothing failed. Use POST /query for filtered reads, and prove your filter works by running it once with a value you know matches nothing: if the row count does not change, the filter is being ignored. See Querying & Pagination.
Run status is one of completed, partial, failed, compensated or compensation_failed. The two that need a human are partial — the run finished but at least one step failed — and compensation_failed, where a rollback itself hit something it could not undo. A run record also carries the failed step id, the ordered list of steps that completed, and the errors from any inverse call that failed. Workflow Engine Reference is the full contract, including what each status means and what the step records hold.

💡 This is where a failure alarm is cheapest to build

Because runs are records, a scheduled query for status in failed, partial or compensation_failed since your last check is a complete failure detector for everything a workflow touches — and workflows are what most integrations run through. One polling job over one object type covers more ground than any other single thing you could build.

⚠️ Read the run record, not the status operation

The workflow service exposes a status operation alongside its run and validate operations. Do not use it to check a run: it does not report the run’s real state, and what it returns will look like a healthy completed run whatever actually happened. The run record is the only accurate answer. Note too that step-level logging is a setting on the workflow service and its writes are best-effort — a run whose step log is empty is not necessarily a run that took no steps, so treat a missing step trail as missing evidence rather than as evidence of nothing.

Connector Runs

One endpoint answers almost every operational question about a connector:
bash
curl "https://api.supero.dev/api/v1/connectors/{connector_id}/status?limit=10" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
It combines whether the connector is enabled, its current status, when it last ran and when it runs next, computed totals — total runs, successes, failures, average duration — and the recent executions with records written and records failed on each. Run-level status is a wider vocabulary than the connector-level one: a run is completed, partial, failed, cancelled or timeout, while the connector itself is active, paused or error. The console shows the last 50 executions on a connector's Run History tab. See Connectors Overview.

⚠️ Confirm a scheduled run actually happened

A schedule you set may not run in every environment. Do not treat a saved cron as evidence of execution — check last_run_at and the recent executions before you depend on it. A connector whose schedule cannot fire comes back with next_run_at set to null and a validation warning explaining why, and the console does not display that warning today, so read the field over the API if a schedule looks inert.

ℹ️ What you cannot see

Run History shows how many rows the last run wrote, not the position an incremental cursor reached, and no connector endpoint returns the cursor. A domain-admin credential can read the connector sync-state object type through CRUD, which does carry the current cursor value per stream; a project- or tenant-scoped credential gets nothing back from it, so it is not something an application can rely on. For everyone else the practical checks are comparing row counts across runs, or triggering a full re-sync when you want a known-good baseline.

Deployments and App Logs

Publishing is asynchronous: the call returns as soon as the build is queued, with a poll URL, and you watch that URL until the app settles. Status is one of none, launching, running, failed, stopped or expired, and a failure always ends in failed with a reason recorded rather than in silence. Publish Your App covers the full progression, including the build and rollout time budgets.

⚠️ Poll the URL you were handed, not the bare project path

Preview and published apps are separate services with separate status. A publish returns its own poll URL; the bare project path reports the preview lane. Poll the wrong one and you will watch a preview forever while your published app comes up — or, worse, report a successful deploy from a preview that was never the thing your users reach. Anything written against the older single-lane behaviour needs updating.
When a deploy fails or an app crashes on startup, the container logs are the fastest answer. The dashboard has a Cloud Deploy Logs page, available to anyone with deployment read permission, and the same data is behind GET /api/v1/preview/project/{domain}/{project}/logs — which reads the preview build's container logs. A published (live) app's own container logs are not exposed through this endpoint; debug a live app from its behaviour and the data it reads and writes.
  • •200 lines by default, up to 500 per request.
  • •One hour of lookback by default, up to 24 hours.
  • •Filterable by severity. A severity value that is not recognised is ignored rather than rejected, so a typo silently widens your filter instead of erroring.
  • •Responses are cached briefly and queries are rate limited per domain; a burst past that returns 429 with a Retry-After header. Auto-refresh is off by default for the same reason.
  • •Each line carries a correlation id alongside the message, the request method, the endpoint, the status code and the duration — so a failing request seen in your app’s logs can be carried straight back to the platform trace.

ℹ️ App logs are a Supero Cloud surface only

The log reader covers apps published to Supero Cloud. For an app deployed into your own AWS or GCP account, the logs are in your account — CloudWatch or Cloud Logging — and Supero does not proxy them. That is usually an improvement: they are yours, they are retained on your terms, and your existing alerting already reaches them. See Deploy to Your Own AWS or GCP.

Triage by Layer

When something is wrong and you do not yet know where, work outward from the request. Each layer has one thing worth checking before you move on.
LayerFirst thing to checkWhere
The request itselfThe status code and the raw body, before parsing anything. A JSON parse error on the error path usually means a 405 from a mistyped URL or an empty path segment built from an unset variable.Errors & Status Codes
A 200 that liedSuccess and the created/updated/failed counts on a write; body.success on a services call. An empty read can be a denial or a scope clamp rather than an empty table.Errors & Status Codes
The credentialThe domain, project, tenant and role actually in force. Assumed scope is wrong more often than assumed permissions.GET /api/v1/auth/session
AuthorizationWhether it is a policy rather than a role. A 403 with an unhelpful message is usually an access rule.Access Policies
The platformThe trace for the correlation id on the failing response.GET /api/v1/logs/trace/{correlation_id}
A workflowThe run record and its step log — which step, what input it sent, what came back, how many retries.Workflow Engine Reference
A connectorlast_run_at and the recent executions. A source that is reachable but returning nothing looks identical to a healthy run with zero rows.Connectors Overview
A published appDeployment status first, then the container logs. A URL that returns 200 is not proof the app is working.Publish Your App
Who changed whatThe audit trail for the window around the change.Security

💡 What to send when you escalate

Your domain name, the project, the exact method and path, the timestamp, the full response body and status, and the correlation id. Most support round-trips are spent establishing those five things — see Contact & Links.

What Is Not Provided

Stated so you can plan for it rather than discover it during an incident. Each of these is a real gap, not an undocumented feature.
Not availableWhat this means for you
No metrics endpointThere is nothing to scrape. No Prometheus endpoint, no StatsD emission, no OpenTelemetry export you can point a collector at. Request rates, latencies and error rates are not exposed as numbers anywhere. If you want them, measure at your own client and record them yourself.
No log streaming or tailingEvery log surface here is a bounded read you poll. There is no websocket, no server-sent-event stream, and no follow mode. The closest approximation is polling with since_seq, which is fine on an interval and abusive in a loop.
No alerting and no failure webhooksNothing pushes to you when something breaks. Webhooks on Supero are inbound only — external systems delivering events in, not Supero calling out on an event. Failure detection is something you build. Note also that the SDK build API accepts a callback URL and a notification email; neither is dispatched to, so a build that finishes or fails will not tell you.
No status page and no uptime commitmentThere is no public status page to check during an incident, and no uptime SLA is published in this documentation. See Compliance & Trust.
No log export or long-term retentionThe recent-activity window evicts as it fills and audit entries default to 90 days. There is no archive, no cold storage and no bulk log export. Anything you need later, you pull and keep.
No pre-built dashboardsThere is no operations console beyond the deployment and connector pages. There is nothing to configure, and equally nothing you have to learn.

Building the missing piece

The gap that matters most in practice is alerting, and it is the cheapest to close because the failure records already exist. A single scheduled job covers most of it:
  • •Query wf:workflow_instance for runs whose status is failed, partial or compensation_failed since your last check.
  • •Query each connector's status endpoint and compare failure_count and last_run_at against what you saw last time.
  • •Query audit_log for authentication failures and rate-limit events above whatever rate is normal for you.
  • •Read the health endpoint of each registered plugin and alert on an open circuit breaker or a falling success rate.
  • •Poll GET /api/v1/logs with levels=error and a since_seq you carry forward, if the endpoint is available on your deployment.
  • •Send the result somewhere you actually read — your own mail or chat integration, or a workflow whose steps do it for you. See Workflows & Automation.

Health checks you can point a monitor at

Three health surfaces exist, and they answer different questions.
EndpointAuthWhat it tells you
GET /health on the API hostNoneWhether the platform is answering. Treat a 200 as up and nothing more — the body is diagnostic and its shape is not a stable contract, so do not parse it.
GET /health or /healthz on your own published appNoneWhether your app came up correctly. Worth reading carefully — see the note below.
GET /api/v1/plugins/{plugin_uuid}/healthPlugin read permissionPer-plugin health for endpoints you registered: a health state, success and failure counts, success rate, average latency, last invocation, and whether the circuit breaker has opened.

⚠️ Your app’s health endpoint reports degraded two different ways

A published app answers /health and /healthz itself, and it can report a degraded state — an incomplete build, seed failures, empty seeded schemas, unconfigured services. One degraded case answers 503; the others answer 200 with a degraded status and a reason in the body. So a monitor that only checks the status code will call a partly-broken app healthy. Check the body’s status field, not just the code, and alert on degraded as well as on non-200.

ℹ️ What a liveness probe does not tell you

None of these confirms your data path works. An uptime monitor pointed at a real authenticated read of your own records — one that asserts a row count rather than a status code — catches far more than a liveness probe, and it is the check that would have caught most of the failure modes described on this page.

ℹ️ One surface that may report itself unavailable

A failure-analytics read API exists under /api/v1/analytics/failures for domain-admin roles, offering summaries, groupings, trends, an event list and a CSV export. It depends on an external analytics backend being configured for the deployment, and where it is not it answers successfully with a configured flag set to false rather than an error. Check that flag before building anything on it, and do not treat its absence as an outage.

How This Differs on Self-Hosted

Running the platform yourself changes what is available in both directions. You get the host, and you owe the operations.
SurfaceOn a self-hosted deployment
Workflow runs and connector runsIdentical — they are records in your own data.
Audit trailNot on by default. Audit logging is a deployment setting rather than something the product turns on for you, and its retention window is configurable. Confirm you can actually read your domain’s trail before you rely on it — an empty read looks the same whether nothing happened or nothing is being recorded.
GET /api/v1/logs and the trace endpointAvailable only where the backing store is configured. Where it is not, the routes are absent and return 404. Verify before relying on them.
Cloud Deploy LogsNot applicable. Your apps are not on Supero Cloud.
Service logsYours, on the host. The platform CLI exposes them directly — see below.
Metrics, alerting, retention, backupsYours to provide. Nothing changes about what the platform does not do, but now nobody else is doing it either.
The installed CLI is the operational entry point on the host. Service state, a health check, and per-service logs are all one command away:
bash
sudo supero status                  # what is running
sudo supero health                  # health check across services
sudo supero logs <service> 200      # recent lines; supero logs --help lists services

⚠️ supero health reports reachability, not health

supero health treats any HTTP 200 from a service's /health as healthy — it does not read the body. A service that answers 200 with a degraded status therefore shows as healthy. For anything load-bearing, read the /health JSON directly and check its status field rather than trusting the CLI's pass/fail.
That is a genuine advantage over the hosted surface: you have the raw logs, with your own retention, feeding whatever collector you already run. On-Premise Deployment covers the install, the system requirements and the troubleshooting table that pairs symptoms with these commands.

⚠️ Two operational defaults to check on day one

Secure the database the installer put on the host — enable authentication and restrict network access to it — before real data goes on it. And confirm the key store is enabled: it is off by default on a self-hosted install, and while it is off, integration credentials are stored in plaintext with no error and no warning. Neither is flagged by anything you will see in normal operation. See Security.

Next Steps