DevelopersREST API ReferenceKeys, Events & Health

Keys, events & health

Reference for the gateway’s system endpoints: the five API-key management endpoints, the raw contract event feed (GET /v1/events), and the liveness probe (GET /v1/health). All paths are relative to the production base URL https://noetherapi-production.up.railway.app.

API key management

Five endpoints manage the full key lifecycle. The first three are public and implement the wallet-challenge issuance flow; the last two require a bearer key. This section summarizes requests and responses β€” for the full signing walkthrough (what the wallet signs, wallet compatibility, error cases), see Authentication.

⚠️

Key issuance is in closed beta. If your wallet address is not on the access list, POST /v1/keys returns 403 {"error":"not_in_beta"}. Check your status first with GET /v1/keys/beta-status. Public endpoints and already-issued keys are unaffected by the gate.

GET /v1/keys/beta-status β€” check issuance gating

Public. Optional query param address (a 56-character Stellar G… address).

curl -s "https://noetherapi-production.up.railway.app/v1/keys/beta-status?address=GBTRADER..."
{ "gated": true, "allowed": false }

gated is true when an access list is configured. allowed is true when issuance is open to everyone, or when the supplied address is on the list.

POST /v1/keys/challenge β€” request a signing challenge

Public. Body: {"address": "G…"} (exactly 56 characters).

curl -s -X POST https://noetherapi-production.up.railway.app/v1/keys/challenge \
  -H "content-type: application/json" \
  -d '{"address":"GBTRADER..."}'
{ "challengeHex": "9f8e...64 hex chars...", "expiresAt": 1720000300000 }

The challenge is 32 random bytes, hex-encoded. It is single-use, expires after 5 minutes (expiresAt is a millisecond epoch), and only one challenge is pending per address β€” requesting a new one invalidates the previous. A failed verification attempt also burns the challenge, so request a fresh one before retrying.

POST /v1/keys β€” exchange a signed challenge for a key

Public, but gated by the closed-beta access list (checked before signature verification).

FieldRequiredNotes
addressyes56-character Stellar G… address
challengeyesThe challengeHex you received
signatureyesThe full base64 signed transaction XDR β€” not a detached signature, despite the name
labelnoFree-text label, at most 64 characters

Success returns 201:

{
  "keyId": "nk_3f9a1c0b8d2e4f6a7b8c9d0e",
  "secret": "64-hex-char-secret...",
  "tier": "standard",
  "owner": "GBTRADER...",
  "createdAt": 1720000000000
}

The secret is shown exactly once β€” only its hash is stored server-side, and there is no way to retrieve it again. Store it immediately. Use the pair as Authorization: Bearer <keyId>:<secret> on REST calls, or in the WebSocket login op.

Errors: 403 {"error":"not_in_beta", ...} (address not on the access list) and 401 {"error":"invalid_signature"} (bad, expired, or reused challenge; wrong signature; fee-bump transaction; wrong first operation). How to construct the signed transaction is covered step by step in Authentication.

GET /v1/keys β€” list your keys

Requires a bearer key. Returns every key with the same owner as the authenticated key, newest first, including revoked ones.

curl -s https://noetherapi-production.up.railway.app/v1/keys \
  -H "Authorization: Bearer nk_your_key_id:your_secret"
{
  "keys": [
    {
      "keyId": "nk_3f9a1c0b8d2e4f6a7b8c9d0e",
      "owner": "GBTRADER...",
      "tier": "standard",
      "label": "trading-bot",
      "createdAt": 1720000000000,
      "lastUsedAt": 1720003600000,
      "revokedAt": null
    }
  ]
}

Timestamps are millisecond epochs. lastUsedAt updates on every successful authentication.

DELETE /v1/keys/{keyId} β€” revoke a key

Requires a bearer key, owner-scoped: a key can revoke any key belonging to the same owner, including itself. Revoked keys fail authentication immediately.

curl -s -X DELETE https://noetherapi-production.up.railway.app/v1/keys/nk_3f9a1c0b8d2e4f6a7b8c9d0e \
  -H "Authorization: Bearer nk_your_key_id:your_secret"

Returns {"revoked": true} on success, or 404 {"error":"key_not_found"} when the key does not exist, belongs to another owner, or is already revoked.

GET /v1/events β€” raw contract events

Public. Returns decoded on-chain contract events from the indexer, newest first. The same underlying event rows also stream live (framed slightly differently) on the WebSocket events channel β€” see WebSocket.

Query paramTypeNotes
topicstringFilter by event topic (see below)
contractstringFilter by emitting contract ID
from_ledgerinteger, at least 0Inclusive lower ledger bound
to_ledgerinteger, at least 0Inclusive upper ledger bound
before_tsinteger, at least 0Pagination cursor: only events with ledger close time strictly less than this unix-seconds value
limitinteger, 1–500Default 50

All parameters are optional. Pagination is cursor-style: take the ledgerCloseTs of the last row and pass it as before_ts on the next request. There are no page tokens or total counts.

curl -s "https://noetherapi-production.up.railway.app/v1/events?topic=funding_applied&limit=1"
{
  "events": [
    {
      "eventId": "0012760575469703168-0000000000",
      "contractId": "CC2HH34Q7GOMNBNPSNSQIIUSYYXLYLOOLMUY3ZTFFBLJ2DENWHGS6GNB",
      "topic": "funding_applied",
      "ledger": 2971053,
      "ledgerCloseTs": 1780867142,
      "txHash": "85895449a4316bea9d27252f3b4e15dbd8f8d3c2c7c27485160ffe791d8182e0",
      "payload": { "topic": "funding_applied", "fundingRate": "-171", "hoursElapsed": "1" },
      "insertedAt": 1780867143077
    }
  ]
}

ledgerCloseTs is unix seconds; insertedAt is a millisecond epoch. The event log preserves history across contract redeployments, so contractId may reference a contract from an earlier deployment (as in this real captured sample) β€” pass the contract filter if you only want events from the currently deployed contracts (see /v1/health). payload is the decoded event body and its shape varies per topic. Market topics include position_opened, position_closed, position_liquidated, order_placed, order_executed, order_cancelled, and funding_applied; vault and referral contracts emit their own topics. The exact on-chain event tuples behind these payloads are documented in Contracts.

GET /v1/health β€” liveness and configuration probe

Public and exempt from rate limits β€” safe to poll from monitoring. Beyond a liveness check, it echoes the resolved contract addresses this gateway deployment actually serves, which is the quickest way to verify what a gateway instance points at.

curl -s https://noetherapi-production.up.railway.app/v1/health
{
  "status": "ok",
  "uptime": 207584.19,
  "version": "0.1.0",
  "network": "testnet",
  "contracts": {
    "market":        { "address": "CAE3U7JKESRWZHPEQ72DVNGOQ6WPA7HSPQZL5YV46NPCE4TMUPAGYMEC", "source": "manifest" },
    "vault":         { "address": "CBLVZZ557ALB342GBQIMC2E3IYXJ5AEL7XSVMPX36CVLKRDQGDH22UL6", "source": "manifest" },
    "noeracleShim":  { "address": "CBY4YLPYEN5GMV4JMVZGCSU433SO5JTEGCS66GIT3EZOUG2CN2TWT6UK", "source": "manifest" },
    "noetherRouter": { "address": "CDH4CY3XMUZ7H73LC3WJYR3AG56U4MTIBMR2WHNORXDTWLLBOQII44S4", "source": "manifest" },
    "usdcToken":     { "address": "CA63EPM4EEXUVUANF6FQUJEJ37RWRYIXCARWFXYUMPP7RLZWFNLTVNR4", "source": "manifest" },
    "noeToken":      { "address": "CD7VRBXIDYP2C2F2AZZL242GY4PRDVDH2BG3LAN2ASXYUXCPHWQJTDP5", "source": "manifest" },
    "vaultFactory":  { "address": "CAZPVI2PLDQJGDDGULG7FSVNYO3B7BTWTTK5OWVH5SSWN7SJ4ZCBTVP5", "source": "manifest" },
    "referral":      { "address": "CCV5NWLGQTCRFFOTHJDQVFOOLAEUPQTWMPTNNHR2EU64BULV77W5GXEE", "source": "manifest" }
  },
  "indexer": { "lastLedger": 2971053, "lastUpdatedAt": 1780867143252, "ledgerAgeSeconds": 2763198 }
}
FieldMeaning
status, uptime, versionLiveness; uptime is process seconds
networktestnet β€” the live deployment runs on Stellar testnet
contractsMap of all eight contracts to {address, source}; source is "manifest" (baked deployment manifest) or "env" (environment override)
indexer.lastLedgerLast ledger the indexer read-side has processed
indexer.lastUpdatedAtMillisecond epoch of the last indexer write
indexer.ledgerAgeSecondsHow far the indexer read-side lags behind now

Endpoints backed by indexer projections (/v1/events, /v1/trades, /v1/leaderboard, /v1/positions/open, and others) can lag behind the chain; live oracle price endpoints do not. Before relying on projection data, check indexer.ledgerAgeSeconds here β€” the real captured sample above shows a heavily lagging indexer (ledgerAgeSeconds β‰ˆ 32 days), which is exactly the condition this field exists to expose. On a fresh deployment with no indexed data yet, the indexer fields are null.

Next steps

  • Authentication β€” the full challenge-signing flow with wallet code
  • WebSocket β€” stream the same events live instead of polling
  • API explorer β€” try these endpoints interactively