API Reference
Conventions

API Conventions

Every endpoint on api.superhero.com is plain JSON over HTTPS. The rules below (base URLs, pagination, shared query parameters, units, errors, rate limits and the signature-based write flow) apply everywhere and are not repeated on the endpoint pages.

Base URLs

NetworkBase URLChain id
Mainnethttps://api.superhero.comae_mainnet
TestnetNot publicly documentedae_uat

Both hosts serve the same paths. Every path is relative to the base and begins with /api/. The only versioned prefix is /api/v2/mdw/*, the indexed chain mirror. See Node & Middleware.

Outside production the server also serves Swagger UI at /api and the OpenAPI 3 document at /api-json.

Authentication

Reads need no credentials. No API key, bearer token, cookie or session:

curl -s "https://api.superhero.com/api/tokens?limit=3&order_by=market_cap&order_direction=DESC"

Writes are authorised by wallet signature. There is no login endpoint and no token to store. Every mutating flow is three steps:

  1. POST the challenge endpoint with your ak_... address. It returns a nonce (or verification_token), a human-readable message and an expiry.
  2. Sign message with the private key for that address.
  3. POST the submit endpoint with the nonce, the signature and the value you are claiming.

The server verifies the signature against the address in the request. Endpoints that use this pattern:

FlowChallengeSubmit
Link an X handlePOST /api/address-links/x/claimPOST /api/address-links/x/submit
Link a bioPOST /api/address-links/bio/claimPOST /api/address-links/bio/submit
Link a websitePOST /api/address-links/site/claimPOST /api/address-links/site/submit
Set a preferred .chain namePOST /api/address-links/prefered-aens-name/claimPOST /api/address-links/prefered-aens-name/submit
Link a Nostr keyPOST /api/address-links/nostr/claimPOST /api/address-links/nostr/submit
Sponsored .chain registrationPOST /api/profile/chain-name/challengePOST /api/profile/chain-name/claim
X-posting reward rescanPOST /api/profile/x-posting-reward/recheck-challengePOST /api/profile/{address}/x-posting-reward/recheck
Push-notification devicePOST /api/notifications/devices/challengePOST /api/notifications/devices
Notification preferencesPOST /api/notifications/preferences/challengePOST /api/notifications/{address}/preferences

Each claim flow has a matching unclaim and unclaim/submit pair, signed the same way. See Profile for request bodies.

The notification feed uses a bearer session token. One wallet signature is exchanged for a token, which is then sent as a normal Authorization: Bearer header:

POST /api/notifications/{address}/feed/challenge   ->  { nonce }
sign the message                                   ->  signature
POST /api/notifications/{address}/feed/session     ->  { token, expiresAt }
GET  /api/notifications/{address}/feed?limit=50    ->  Authorization: Bearer {token}

GET /api/notifications/web-push/vapid-public-key is public and needs no token.

Two endpoints take an API key. POST /api/trending-tags and PATCH /api/dex/tokens/{address}/listed accept it as either header:

x-api-key: YOUR_KEY
Authorization: Bearer YOUR_KEY

A missing or wrong key is a 401.

POST /api/affiliations takes no credential. It accepts a sender_address and a list of codes and returns a generated ten-character affiliation code. Its sibling POST /api/affiliations/invites/{code}/{provider}/{access_code} verifies access_code with GitHub, Google or X before handing out a private invite code.

Pagination

Most list endpoints return items plus meta:

{
  "items": [],
  "meta": {
    "currentPage": 1,
    "itemCount": 20,
    "itemsPerPage": 20,
    "totalItems": 4213,
    "totalPages": 211
  }
}
FieldTypeMeaning
currentPagenumberThe page just returned (1-based)
itemCountnumberRecords in this response
itemsPerPagenumberThe effective page size
totalItemsnumberTotal matching records
totalPagesnumberTotal pages at this page size

Pass page (1-based) and limit to walk it.

The limit cap is 100

100 is both the default and the maximum. What happens when you exceed it depends on the endpoint family:

Endpoint familylimit=101page=0
/api/tokens, /api/accounts, /api/posts, /api/trending-tags, /api/tips, /api/invitations and the other list endpoints400: Limit must be between 1 and 100 (wording varies per endpoint)400: Page must be greater than or equal to 1
/api/v2/mdw/*400: Maximum limit is 100 items per request400: Page must be greater than or equal to 1
/api/dex/* listsSilently clamped to 100Silently clamped to 1

Read meta.itemsPerPage rather than assuming your limit was honoured. page on /api/v2/mdw/* is capped at 1,000,000.

Invalid order_by

Most list endpoints hold an allow-set and return 400 Invalid order_by value: {value} for anything outside it. GET /api/tokens is the exception: an unrecognised order_by falls back to market_cap and an unrecognised order_direction to DESC, with no error.

Responses that are not the envelope

EndpointShape
GET /api/tokens/{address}/historyA bare JSON array. Still takes page and limit; you have reached the end when a page comes back empty
GET /api/accounts/{address}/portfolio/historyA bare JSON array of snapshots, bounded by startDate, endDate and interval rather than by pages
GET /api/accounts/leaderboarditems plus a different meta: page, limit, totalItems, totalPages, window, sortBy, sortDir and timeFilter. Note page, not currentPage
GET /api/feedKeyset pagination: items plus next_cursor. Pass the cursor back as cursor; there is no page number

The analytics series endpoints return bare arrays and the totals endpoints return a scalar or a small object. See Analytics.

Shared query parameters

ParameterApplies toNotes
pagelist endpoints1-based
limitlist endpointsCapped at 100
order_bymost list endpointsEnum per endpoint; the value set is not shared
order_directionmost list endpointsASC or DESC
searchtokens, posts, topics, accounts, DEX tokens, DEX pairsSubstring match, server-side
convertToprice and history endpointsSee below
intervalhistory endpointsSeconds, not a keyword
includes/api/transactionsOnly token, which inlines the token object

Parameter casing

Query keys are mostly snake_case, with exceptions. An unrecognised key returns unfiltered data rather than an error, so match the spelling per endpoint:

SpellingUsed by
start_date / end_dateAll /api/analytics/*, GET /api/tokens/{address}/transactions, /api/bcl-affiliation/analytics*
startDate / endDate/api/accounts/leaderboard, all /api/accounts/{address}/portfolio/*
from_date / to_dateGET /api/dex/transactions
convertToEvery endpoint that takes it
sortBy / sortDir/api/accounts/leaderboard only. Everything else uses order_by / order_direction

Currency conversion

Endpoints that accept convertTo take one of ae (default), usd, eur, aud, brl, cad, chf, gbp, xau. The same nine keys make up the PriceDto object returned wherever a value has a price:

{ "ae": 0.0421, "usd": 0.0013, "eur": 0.0011, "aud": 0.002,
  "brl": 0.0071, "cad": 0.0018, "chf": 0.001, "gbp": 0.00097, "xau": 0.0000004 }

Live AE fiat rates come from GET /api/coins/aeternity/rates, a flat map of lowercase currency code to number.

Units

The API mixes two conventions. Values in trade records and OHLC candles are in whole units (AE, whole tokens). Values read live from a contract (a token's market_cap, total_supply and dao_balance) are raw (aettos, base units).

Field familyUnitConversion
price, sell_price, and every PriceDto derived from them (price_data, sell_price_data, performance current / high / low)AEnone
Everything on /api/transactions: volume, total_supply, protocol_reward, and the amount, unit_price, buy_price, previous_buy_price, market_cap price objectswhole tokens / AEnone
TokenDto.market_cap, market_cap_data, dao_balanceaettosdivide by 1e18
TokenDto.total_supply, circulating_supply, holder balancetoken base unitsdivide by 10 raised to decimals (18 for every token the factory mints)
Invitation amount, and the total_amount_ae aggregates built from itAEnone

market_cap therefore means two different scales depending on where you read it: on a token it is aettos, on a transaction or an OHLC candle it is AE. They differ by exactly 1e18.

Every key of a PriceDto is on the same scale as its ae key. Each fiat key is ae * rate[code], not a re-denomination: if ae is in aettos, so is usd. When rates are unavailable every key except ae is null.

decimals is a string in TokenDto, not a number. Parse it before using it as an exponent. Most numeric token fields are strings for the same reason: they are big integers that do not survive a double.

Errors

Failures are ordinary HTTP status codes with a JSON body carrying statusCode, message and error.

StatusWhen you will see it
400Invalid parameters: limit or page out of range, an order_by outside the allow-set, a malformed ak_/ct_/th_ argument, a search longer than 100 characters, a start date after an end date, or an unknown property in a validated request body
401A missing or wrong API key on the two key-guarded endpoints, or a missing or expired feed session token
404Unknown token, post, account or name
429The per-IP rate limit on the routes listed below; and POST /api/profile/{address}/x-posting-reward/recheck, which enforces a once-per-24h cap and returns a body carrying nextAllowedAt

Request bodies and DTO-bound query parameters (the leaderboard, portfolio history) are validated strictly: an unknown property is a 400, and declared types are coerced ("5" becomes 5). Individually bound query parameters (most list endpoints) ignore unrecognised keys silently.

Rate limits

PropertyValue
Allowance10 requests per minute
Window60 seconds, fixed, starting at your first request
Bucket keyclient IP plus route path, per route, not per API
Response when exceeded429
Body{ "status": 429, "message": "Rate limit exceeded. Maximum 10 requests per minute.", "retryAfter": <seconds> }

retryAfter is a field in the JSON body, in seconds. There is no Retry-After header and no X-RateLimit-* headers. Because the key includes the route path, the budget is spent separately on each guarded endpoint.

Guarded routes

Route
GET and GET {id} on /api/v2/mdw/txs, /api/v2/mdw/micro-blocks, /api/v2/mdw/key-blocks, /api/v2/mdw/sync-state, /api/v2/mdw/plugin-sync-state
GET /api/announcements
POST /api/notifications/devices/challenge, POST /api/notifications/devices, DELETE /api/notifications/devices
POST /api/notifications/preferences/challenge, GET and POST /api/notifications/{address}/preferences
POST /api/notifications/{address}/feed/challenge, POST /api/notifications/{address}/feed/session
GET /api/notifications/web-push/vapid-public-key
GET /api/profile/{address}/x-posting-reward, POST /api/profile/x-posting-reward/recheck-challenge, POST /api/profile/{address}/x-posting-reward/recheck, POST /api/profile/{address}/x-reward/referral-link
POST /api/profile/chain-name/challenge, POST /api/profile/chain-name/claim
/api/rooms/* and the token-gated-room recheck and metrics routes

Everything else (tokens, transactions, accounts, analytics, DEX, posts, trending tags, affiliations, search and feed) is unthrottled. Poll considerately and prefer the WebSocket for change notification.

CORS

ALLOWED_ORIGINSAccess-Control-Allow-OriginCredentials
unset or empty (the default)every origindisabled
*every origindisabled
a comma-separated listjust those originsenabled

Allowed methods are GET, POST, PUT, PATCH, DELETE, OPTIONS. A browser page on any origin can call api.superhero.com directly. The same setting governs the Socket.IO gateways. Responses are gzipped.

Caching

Several endpoints are served from an in-process cache rather than recomputed per request:

EndpointCached for
GET /api/tokens with order_by=trending_score15 minutes
GET /api/tokens, any other sort60 seconds
GET /api/tokens/{address}3 seconds
GET /api/tokens/{address}/trending-eligibility3 seconds
GET /api/tokens/{address}/holders, .../rankings10 seconds
GET /api/tokens/{address}/performance, .../performance-raw60 seconds
GET /api/accounts60 seconds
GET /api/feed15 seconds
GET /api/analytics/total-unique-users20 seconds
GET /api/accounts/leaderboardup to 60 seconds. meta.timeFilter.start / .end are the times the cache entry was filled

Some values are recomputed on a schedule: token performance and rank every five minutes, the daily analytics rows hourly, AE fiat rates every ten minutes, and trending scores as described on Trending.

Versioning and deprecation

There is no version negotiation. The OpenAPI document declares version 1.0, GET /api/stats reports the deployed package version as apiVersion, and no header or path segment selects a version. /api/v2/mdw/* is a name inherited from the middleware's own API.

Deprecation is by annotation. A retired parameter keeps validating and stops having an effect:

WhatStatus
GET /api/contractsDeprecated. Still returns a single-element array naming the community factory. Use GET /api/factory
includeOnChain on both profile read endpointsAccepted and ignored; profiles are served from indexed data
window on GET /api/posts/popularAccepted and ignored; popular ranking is all-time
points and maxCandidates on GET /api/accounts/leaderboardAccepted and ignored

Related

  • API Reference: the full endpoint index
  • Tokens API: the TokenDto these unit rules apply to, field by field
  • Accounts API: the portfolio and leaderboard endpoints with the camelCase date parameters
  • Profile API: the challenge/signature write flow end to end
  • Node & Middleware: when the REST API does not have the data