XCH Options

api reference
https://xchoptions.com

Overview

Every endpoint is public JSON. They answer a caller that has no page open and no wallet paired — a marketplace, a bot, or another service — so nothing is authorized on a session address and nothing requires a key.

Endpoint Limit
POST /api/offers 30 / min
POST /api/options/price 60 / min
GET /api/offers/{offerId} 60 / min

Every amount is a decimal string with the precision it is written at, never a float: "0.01" XCH at precision 12, "1000" mojos of wUSDC.b at precision 3. Ids are 64-character hex. Unix times are seconds, and null is always an answer — a missing expiry, a missing sample — rather than an error.

POST /api/offers 30 requests / minute

Import an offer

Decodes a maker's offer1… string, substantiates its option on chain when this ledger has never seen it, and records both so the site can advertise the offer. The offer string is the source of truth: the price, the expiry and the option's size and strike are read out of the spends, never from the request.

Re-importing the same string updates one row rather than adding a second, because an offer is identified by its own content — hence the same offerId comes back. An imported option has no owner, so it appears on the market chain but in nobody's positions.

The desk's Upload offer button posts here too — same throttle, same answer, and the id it returns is the page the browser is sent to.

Request application/json
{
  "offer": "offer1qqz86…"
}
Field Type Notes
offer string Required. The complete offer string, 8–16384 characters.
Example curl
curl -sS -X POST https://xchoptions.com/api/offers \
  -H 'Content-Type: application/json' \
  -d '{"offer":"offer1qqz86…"}'
200 OK application/json
{
  "offer": {
    "offerId": "3mF7pQ2xLkWs…",
    "launcherId": "9f2c8d…",
    "contractPuzzleHash": "b7d1e3…",
    "requestedAssetId": "dddd…dd",
    "requestedAmount": "1000",
    "requestedPrecision": 3,
    "requestedTicker": "wUSDC.b",
    "fee": 0,
    "expiresAtUnix": 1762145948,
    "expiresOnChain": true
  },
  "option": {
    "optionId": "9f2c8d…",
    "side": "call",
    "size": "0.01",
    "sizeTicker": "XCH",
    "strike": "1",
    "strikeTicker": "wUSDC.b",
    "expiresAtUnix": 1762145948,
    "created": true
  }
}
Response field Notes
offer.offerId base58 of the sha256 of the offer string — the row's identity, not the offer's on-chain id.
offer.launcherId The option's launcher id in hex. The price endpoint takes its option1… address; this value is accepted there too.
offer.requestedAmount What the maker asks for, in mojos of requestedAssetId at requestedPrecision.
offer.fee Mojos the offer carries for the taker's transaction, as an integer.
offer.expiresAtUnix null when the offer asserts no time limit.
offer.expiresOnChain false when the offer carries no time assert at all — it stays takeable until its coin is spent.
option.optionId The launcher id in hex — the same value as offer.launcherId.
option.side call when the contract holds XCH, put when it holds the strike asset.
option.size In sizeTicker — the underlying the contract locks, total for the contract.
option.strike Total strike for the whole contract, not per unit. Divide by size for the per-XCH price.
option.created true when this import is what put the option in the ledger.
  • 422 — the string is not an offer, its time assert has already passed, or the chain could not substantiate the option it names.
  • 503 — the node could not be reached. Not the caller's fault, and nothing was written.
  • 429 — over 30 requests in a minute.
POST /api/options/price 60 requests / minute

Suggested price

What the model says one recorded option is worth. The number comes from the recorded spot and the realized volatility of the stored hourly series, and nothing is written back — it is an input to a decision, not a quote this site will honour.

Request application/json
{
  "option_id": "option1nxh6jx…"
}
Field Type Notes
option_id string Required. The option's option1… address — the id a wallet shows. A 64-character hex launcher id is the same 32 bytes and is accepted too, as is the camelCase optionId spelling.

An id this ledger has never seen is not refused. The launcher carries the terms — size, strike, side, expiry — so the option is read from the chain, recorded, and priced in the same call; from then on it is one query. A launcher that is not an option, or that the chain has never seen, is a 404.

Example curl
curl -sS -X POST https://xchoptions.com/api/options/price \
  -H 'Content-Type: application/json' \
  -d '{"option_id":"option1nxh6jx…"}'
200 OK application/json
{
  "option": {
    "optionId": "option1nxh6jx…",
    "launcherId": "9f2c8d…",
    "side": "call",
    "size": "10",
    "sizeTicker": "XCH",
    "strike": "13",
    "strikeTicker": "wUSDC.b",
    "expiresAtUnix": 1769860800
  },
  "basis": {
    "spot": 2.0,
    "sigma": 0.6142,
    "riskFreeRate": 0.047646653467353
  },
  "suggested": {
    "primary": { "amount": "7", "ticker": "wUSDC.b" },
    "secondary": { "amount": "3.5", "ticker": "XCH" }
  }
}
Response field Notes
option.optionId The option's option1… address, echoed back. option.launcherId is the same option in hex.
basis.spot The last recorded spot per XCH, or null when none has been recorded.
basis.sigma Annualized realized volatility from the hourly series, or null when that series is too short to form a sample.
basis.riskFreeRate The rate the model was fed, as a continuously compounded fraction. It is the latest published 10-year Treasury par yield, refreshed hourly, so a suggestion above can be reproduced from this payload.
priceable false when the contract's legs are not XCH and the deployment's quote asset — the desk has no per-XCH reading for it, so there is no suggestion even with a spot recorded.
suggested The same premium in both legs, as decimal strings; both are totals for the contract. primary is always the deployment's quote asset and secondary always XCH — on a put those are the contract's underlying and strike legs respectively, not a reversed meaning, so a caller displays them in the order given. null when the contract is not priceable, or when there is no spot or no size to price from — an answer, not an error.

The same quote has a page: /option/{optionId} states the estimate and every input behind it, for a person rather than a bot.

A missing sigma is not fatal: with no volatility sample the model falls back to intrinsic value, so the answer is what the option is worth right now and no more. A contract whose legs are not XCH and wUSDC.b is not priced at all — priceable says so rather than the model guessing at a unit.

  • 404 — no option is recorded for that id, and the chain does not show one.
  • 422 — option_id is missing, or is neither an option1… address nor a 64-character hex launcher id.
  • 429 — over 60 requests in a minute.
  • 503 — the Chia node could not be reached, so an unrecorded id could not be checked. Retry.
GET /api/offers/{offerId} 60 / min

Fetch a live offer

The taker's side. Returns the offer string a taker hands their wallet, for as long as the chain is still advertising that row — the offer is public, like the file a maker would send by hand.

Example curl
curl -sS https://xchoptions.com/api/offers/3mF7pQ2xLkWs…
200 OK application/json
{
  "offer": "offer1qqz86…"
}

404 once the offer is no longer live — taken, cancelled, or past its time assert. The offerId is the one POST /api/offers answered with.

For a person, the same id has a readable breakdown at /offers/{offerId} — the option, what the offer gives and asks for, and when it stops existing. It renders offers that are no longer live too, so a dead link can still say why it died.

Errors

A validation failure is the framework's own shape: a message for a human and a per-field list for a parser. Everything else is a single message.

422 Unprocessable Content application/json
{
  "message": "That offer has already expired.",
  "errors": {
    "offer": ["That offer has already expired."]
  }
}
Status Body When
422 message + errors A field is missing or unusable, the offer has expired, or the chain cannot substantiate it.
404 message No option or live offer for that id.
429 message Rate limit exceeded; Retry-After states the wait in seconds.
503 message The Chia node could not be reached. Retry; nothing was recorded.

Rate limits

Limits are per client address, counted in a rolling minute. Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining, so a caller can back off before it is refused rather than after.

Endpoint Requests / minute
POST /api/offers 30
POST /api/options/price 60
GET /api/offers/{offerId} 60

The chain reads the page's own wallet scan makes — /chain/{endpoint} — are an internal proxy for this browser, not part of the public API, and are not supported for third-party use.