OwlViz · API docs

Asset & collection intelligence for Solana.

Base URL: https://api.owlviz.dev

Try example mint , agents, or quick start.

Monitoring & events

**OwlViz** change monitoring watches assets and collections for semantic metadata, health, authority, and image changes. Webhooks deliver monitor and market change events asynchronously (HMAC-signed POST, worker retries).

Base URL: https://api.owlviz.dev

Public reads vs writes

CapabilityAuthNotes
List/read monitors, events, webhooks, deliveriesAPI key or admin**Anonymous → 401** AUTOMATION_AUTH_REQUIRED. Keyed callers see **owned** rows only. Admin lists **ownerless ops** rows only
Read market watch by idAPI key or adminSame ownership rules as monitors
Collection market-watch statuspublic (ops watches only)GET /v1/collections/:address/market-watch returns **ownerless** watches without auth; account-owned watches require key/admin (**404** when unauthenticated)
Create/update/disable monitorsAPI key or adminPortal ov_live_… key (plan quota, min **3600s** interval) or ops admin token for ownerless operator monitors
Manual checkBearer adminPOST /v1/monitors/:id/check — **not** available to API keys
Webhook mutationsAPI key or adminPortal ov_live_… key on Builder+ (plan quota) or ops admin token

**Self-serve change monitor writes** use your portal API key:

http
Authorization: Bearer ov_live_…

Plan quotas (activeChangeMonitors): Free **1**, Builder **5**, Pro **20** (active, non-disabled monitors). Minimum intervalSeconds for keyed creates/updates is **3600**. Manual POST /v1/monitors/:id/check remains **admin-only** (MONITORING_ADMIN_TOKEN).

**Operator / legacy monitors** (owner_account_id null) are created with the admin token and may use tighter poll intervals configured server-side. Monitor-event webhook fanout for ownerless monitors delivers only to **ownerless** (ops) webhook endpoints. Account-owned monitors fan out only to webhook endpoints with the same owner_account_id. Market-event webhook fanout is unchanged (all matching subscriptions).

**Webhook and Market Watch writes** use your portal API key (ov_live_…) within plan quotas, or the same admin token for operator workflows. Manage subscriptions from the dashboard at /developer/webhooks, /developer/change-monitors, and /developer/market-watches without pasting keys into the browser.

Production default: PUBLIC_MONITORING_WRITES=false — **admin-path** monitor writes return **403** MONITORING_WRITES_FORBIDDEN when disabled (API key writes still work). Set MONITORING_ADMIN_TOKEN server-side only — never in NEXT_PUBLIC_* or client bundles.

The public scanner is **read-only** for change monitors — no create/check UI. Monitor, webhook, and Market Watch CRUD live in the developer portal. On asset/collection scanner pages, the Monitoring panel loads **your** change monitors via signed-in portal session (BFF); signed-out visitors see a sign-in CTA instead of anonymous monitor lists.

Monitor endpoints

MethodPathAuth
POST/v1/monitorsAPI key or admin write
GET/v1/monitorsAPI key or admin (owned / ops-only lists)
GET/v1/monitors/:idAPI key or admin (404 cross-tenant)
PATCH/v1/monitors/:idAPI key or admin write
DELETE/v1/monitors/:idAPI key or admin write (soft disable)
GET/v1/monitors/:id/eventsAPI key or admin (404 cross-tenant)
POST/v1/monitors/:id/checkadmin write only

Create monitor (self-serve)

bash
curl -sS -X POST "$API_BASE/v1/monitors" \
  -H "Authorization: Bearer ov_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "targetType": "asset",
    "target": "C5gHBKXwA8jduXNk3HyAVLnLBN6PEM8fTqkNNh5uyyjJ",
    "intervalSeconds": 3600,
    "events": ["metadata.changed", "health.degraded"]
  }'

Operator-only create (ownerless legacy monitor, tighter intervals allowed):

bash
curl -sS -X POST "$API_BASE/v1/monitors" \
  -H "Authorization: Bearer $MONITORING_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "targetType": "asset",
    "target": "C5gHBKXwA8jduXNk3HyAVLnLBN6PEM8fTqkNNh5uyyjJ",
    "intervalSeconds": 3600,
    "events": ["metadata.changed", "health.degraded"]
  }'

Monitor fields: targetType (asset | collection), target (mint/address), intervalSeconds, events (non-empty array from allowed enum).

Serialized monitor includes waitingForIndex (true when a collection monitor awaits indexing), lastCheckedAt, nextCheckAt, consecutiveFailures, etc.

Event types (actual enum members)

Asset events

TypeMeaning
metadata.changedSemantic metadata change
metadata.uri.changedOn-chain metadata URI changed
metadata.failedOff-chain metadata resolution failed
metadata.recoveredOff-chain metadata recovered
image.failedImage reliability failure
image.recoveredImage reliability recovered
image.changedImage URI/content change
health.degradedHealth band worsened
health.improvedHealth band improved
authority.changedAny authority field changed
mint_authority.changedMint authority changed
freeze_authority.changedFreeze authority changed
update_authority.changedUpdate authority changed
permanent_delegate.changedPermanent delegate changed
token_program.changedToken program changed
collection.changedEmbedded collection ref changed
owner.changedOwner changed

Collection events

TypeMeaning
collection.health.degradedAggregate health band worsened
collection.health.improvedAggregate health band improved
collection.asset_count.changedIndexed/discovered count changed
collection.verification.changedVerification counts changed
collection.standard_distribution.changedMetadata standard buckets changed
collection.token_program_distribution.changedToken program buckets changed

Webhook test

TypeMeaning
webhook.testTest delivery only

Event listing

GET /v1/events — global feed with filters:

QueryDescription
targetMint or collection address
targetTypeasset or collection
eventTypeEvent type string
severityinfo, warning, error, critical
sinceISO timestamp
limitPage size
cursorPagination cursor

Event payload:

json
{
  "data": [
    {
      "id": "...",
      "monitorId": "...",
      "targetType": "asset",
      "target": "...",
      "type": "health.degraded",
      "severity": "warning",
      "summary": "...",
      "before": {},
      "after": {},
      "diff": { "changes": [] },
      "snapshotIdBefore": "...",
      "snapshotIdAfter": "...",
      "occurredAt": "...",
      "createdAt": "..."
    }
  ],
  "pagination": { "nextCursor": null }
}

Semantic diff behavior

  • **Baseline on first success** — the first successful check stores a baseline; later checks diff against it.
  • **Excluded operational fields** — cache timestamps, transient resolver diagnostics, and similar fields are excluded from semantic diffs.
  • **Attribute/creator normalization** — attributes and creators are normalized before comparison to reduce false positives.
  • **Health band hysteresis** — health events fire on band changes (healthy / warning / degraded / critical), not every point change.
  • **Image/metadata hysteresis** — image and metadata failure/recovery events use hysteresis to avoid flapping.
  • **Event fingerprints** — each persisted event gets a deterministic SHA-256 fingerprint from monitorId, event type, and semantic before/after state hashes. The database enforces a unique index on fingerprint with ON CONFLICT DO NOTHING, so repeated checks that detect the same semantic state do not insert duplicate events or re-trigger webhook delivery storms. Content hashes also participate in image change detection when available.

Image monitoring limitation

Asset image checks read **stored media-recovery state** from the database rather than actively revalidating media on every monitor cycle. Events may lag until media recovery runs elsewhere.

Webhooks

See [webhooks.md](../webhooks.md) for delivery headers, HMAC verification, retries, and payload shape.

Webhook write routes accept a portal API key (Builder+ quota) or ops admin token. POST /v1/webhooks returns the signing secret **once** at creation — store it securely.

Subscription event types

Monitor events (asset/collection), market change events (spot_floor_changed, reference_floor_changed, provider_floor_changed, provider_status_changed), wildcard *, and synthetic webhook.test.

Delivery records (Sprint 6.2)

MethodPathDescription
GET/v1/webhooks/:idEndpoint plus deliveryHealth summary (last status, attempt, error, counts)
GET/v1/webhooks/:id/deliveriesHistory (?limit=, `?status=failed\dead\…`)
GET/v1/webhooks/:id/deliveries/:deliveryIdSingle delivery inspection

Each delivery includes monitorEventId or marketEventId, status, attempt, nextRetryAt, lastErrorCode, httpStatus, and responseTimeMs. Failed deliveries retry with exponential backoff until WEBHOOK_MAX_ATTEMPTS, then move to terminal dead. See [webhooks.md](../webhooks.md) for the full lifecycle.

Market change events (read API)

Durable market events are listed publicly — they are **not** mixed into GET /v1/events (monitor feed):

MethodPathDescription
GET/v1/market-eventsGlobal feed with filters
GET/v1/market-events/:idSingle event
GET/v1/collections/:address/market/eventsCollection-scoped feed

Event types: spot_floor_changed, reference_floor_changed, provider_floor_changed, provider_status_changed. See [Collections — Sprint 6](./collections.md#sprint-6--market-change-events-internal-durable-events) for detection semantics.

Subscribe to push delivery with POST /v1/webhooks and market event types in events. Example: [13-market-webhook.sh](./examples/13-market-webhook.sh).

Collection monitor freshness

COLLECTION_MONITOR_FRESHNESS_SECONDS (default **3600**): collection monitors treat indexes older than this threshold as stale and re-enqueue background indexing. Monitors may report waitingForIndex: true until coverage is sufficient.