Owlviz · API docs

Reliable asset data for Solana.

Base URL: https://solana-metadata-platform-production.up.railway.app

Try example mint or start with quick start.

Errors & rate limits

All API errors use a consistent JSON envelope:

json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message."
  }
}

Unexpected server errors may use a legacy Fastify shape without error.code. Client code should handle non-2xx responses defensively.

Stack traces are never returned to clients.

Rate limiting

When Upstash Redis is configured, public asset and collection routes are rate limited per client IP using fixed-window counters in Redis.

Defaults (may change)

VariableDefaultMeaning
RATE_LIMIT_WINDOW_SECONDS60Window length in seconds
RATE_LIMIT_MAX120Global requests per window per IP
RATE_LIMIT_ROUTE_MAX60Additional cap per expensive route template

Production asset GET /v1/assets/:mint exposes X-RateLimit-Limit: 60 from the **per-route** counter (the tighter of global + route limits is enforced).

Tiers

**Expensive** (global + per-route counters):

  • GET /v1/assets/:mint (+ /health, /inspection, /media)
  • POST /v1/assets/:mint/media/recover
  • GET /v1/collections/:address (+ /assets, /health, /market)
  • POST /v1/collections/:address/index

**Lightweight** (global counter only):

  • GET /v1/assets/:mint/snapshot, /snapshots
  • GET /v1/collections/:address/index
  • GET /v1/collection-index-jobs/:jobId

Exempt:

  • GET /health

**Not rate limited** (no matching rule):

  • Monitor, event, and webhook routes

Response headers

HeaderDescription
X-RateLimit-LimitMaximum requests in the current window (merged limit)
X-RateLimit-RemainingRemaining requests
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds to wait (429 only)

429 envelope

json
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Please retry after the window resets."
  }
}

Fail-open

If Redis errors during a rate limit check, the request is **allowed** and a warning is logged (RATE_LIMIT_CHECK_FAILED). Rate limiting is disabled entirely when Redis is not configured.

Client IP identity

Rate limit keys hash the resolved client IP. X-Real-IP is honored **only** when the socket peer is in TRUST_PROXY_CIDRS (includes 100.0.0.0/8 on Railway). Untrusted peers ignore spoofed X-Real-IP. **X-Forwarded-For is not used** for rate-limit identity.

Error catalog

HTTPCodeMeaningDeveloper action
400INVALID_ASSET_IDMint is not a valid Solana public keyFix the address parameter
400INVALID_COLLECTION_ADDRESSCollection address invalidFix the address parameter
400INVALID_QUERY_PARAMETERBad query valueFix query string
400INVALID_MONITOR_TARGETMonitor target invalidProvide valid mint/address
400INVALID_MONITOR_INTERVALInterval out of rangeUse allowed interval
400INVALID_EVENT_TYPEUnknown event type for targetUse enum values from monitoring docs
400INVALID_WEBHOOK_URLWebhook URL blocked or malformedUse HTTPS URL passing SSRF checks
401MONITORING_AUTH_REQUIREDMissing/invalid Bearer tokenSend Authorization: Bearer <private-beta-token>
403MONITORING_WRITES_FORBIDDENWrites disabled on deploymentUse read APIs or request beta access
404ASSET_NOT_FOUNDAsset not found upstreamVerify mint exists
404SNAPSHOT_NOT_FOUNDNo persisted snapshotWait for persistence or use live resolve
404COLLECTION_NOT_FOUNDCollection not registeredPOST .../index first
404COLLECTION_NOT_INDEXEDIndex not startedStart indexing
404INDEX_JOB_NOT_FOUNDJob ID unknownVerify job ID
404MONITOR_NOT_FOUNDMonitor or event not foundVerify ID
404WEBHOOK_NOT_FOUNDWebhook not foundVerify ID
409INDEX_ALREADY_RUNNINGActive job + refresh=truePoll existing job
429RATE_LIMITEDToo many requestsBack off using Retry-After
429MONITOR_LIMIT_REACHEDMax active monitorsDelete or pause monitors
502UPSTREAM_ERRORProvider failureRetry with backoff
502UPSTREAM_INVALID_RESPONSEProvider returned invalid dataRetry later
502WEBHOOK_DELIVERY_FAILEDTest delivery failedFix receiver endpoint
503SERVICE_NOT_CONFIGUREDResolver not configuredContact operator
503PERSISTENCE_UNAVAILABLEDatabase not configuredEnable persistence or use live routes
503MONITORING_WRITES_NOT_CONFIGUREDWrites enabled but no admin tokenServer configuration issue
504UPSTREAM_TIMEOUTProvider timeoutRetry with backoff

CORS

When CORS_ALLOWED_ORIGINS is set, browser requests must originate from an allowed origin. Exposed headers include rate limit and cache headers listed above.