For the complete documentation index, see llms.txt. This page is also available as Markdown.

API / Developers

Hilo Public API (v1)

Create and read Hilo prediction markets programmatically with the v1 REST API. Markets you create here go live immediately and follow the same rules, rewards and side effects as markets created in the web app.

Status: beta. The endpoints below are live. The topic vocabulary is still being curated, so a name you expect may return TOPIC_NOT_FOUND — fetch GET /api/v1/topics for the accepted set.


Quick start

curl -X POST https://hilo.com/api/v1/markets \
  -H "Authorization: Bearer $HILO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Will the Fed cut rates before July 2027?",
    "description": "Resolves YES if the FOMC announces a cut to the target range at or before the July 2027 meeting.",
    "options": ["Yes", "No"],
    "marketType": "event",
    "topics": ["Economy", "Finance"],
    "filterCountries": ["United States"],
    "estimatedEndDate": "2027-07-31T00:00:00.000Z"
  }'

Authentication

Every request carries a key as a bearer token:

Create keys in Settings → API Keys. Premium and Premium+ only. The plaintext key is shown once at creation and never again — only a hash is stored. Lose it and you create a new one.

Keep your key on a server. Never ship it in a browser, a mobile app, or anything a user can read. A key acts as you: it creates markets under your account, spends your daily allowance, and earns your XP and HIQ. There is deliberately no CORS header on /api/v1, because a key should never be in a position to be sent from a page.

Your key only works while your subscription is active. If it lapses, the key stops working that day — it is not a standing grant.

Scopes

Scope
Grants

markets:read

GET endpoints

markets:write

POST endpoints

Both are granted by default. Untick "Allow creating markets" when creating a key to get a read-only one. A request outside its scope returns 403 INSUFFICIENT_SCOPE with the required and granted scopes in details.


POST /api/v1/markets

Creates a market. It is live immediately — there is no approval queue, matching the web app.

Request

The JSON body must be 1 MB or smaller, and the request must include a Content-Length header.

Field
Type
Required
Notes

title

string

yes

1–120 characters

description

string

yes

1–5000 characters, used verbatim

options

string[]

yes

2–20; blanks are dropped before counting

marketType

"event" | "opinion" | "multiwinner"

yes

topics

string[]

yes

Non-empty, max 10. Names, not ids. First becomes the primary topic

newTopic

string | null

no

Propose one topic that does not exist yet. See below

filterCountries

string[]

no

Full country names

statisticsCountries

string[]

no

Full country names

estimatedEndDate

ISO 8601 | null

no

Must be in the future, within 5 years. Ignored for opinion

locksAt

ISO 8601 | null

no

Must be ≤ estimatedEndDate

isMultiSelect

boolean

no

Premium+ only

isNsfw

boolean

no

isAnonymous

boolean

no

Response — 201

Idempotency

Send an Idempotency-Key header — any client-generated string up to 255 characters. Strongly recommended. Without one, a request that times out and gets retried creates a second market.

  • Same key replayed after success → the original 201 with an identical body, no second market.

  • Same key while the first request is still in flight → 409 IDEMPOTENCY_CONFLICT.

  • Keys are remembered for 24 hours.

  • A request that fails releases its key immediately, so you can retry at once.


Topics

Topics are addressed by name, case-insensitively: "NBA", "nba" and "Nba" are the same topic, and "PREMIER LEAGUE" resolves to Premier league.

The first name in topics becomes the market's primary topic — the one shown on the card. The rest are additional tags.

Only the curated vocabulary is accepted. Fetch it from GET /api/v1/topics; do not assume a name works because you saw it on the site, as topics auto-generated on existing markets are not addressable unless Hilo has curated them.

Common alternate names resolve automatically — send Crypto and the market is tagged Cryptocurrency. GET /api/v1/topics lists every such mapping under aliases, and it is the only place to discover them, because where an alias exists there is no error to tell you.

An unknown name returns 400 TOPIC_NOT_FOUND naming the offending values.

Proposing a new topic

If a topic genuinely does not exist, send it as newTopic:

Rules, each of which matters:

  • topics must still carry at least one curated name. A market tagged only with a proper noun nobody browses is invisible; the new topic is additive detail.

  • One per market, and at most 3 per day. Requests proposing a new topic are processed one at a time per user; a concurrent one gets 429 RATE_LIMITED with a short Retry-After.

  • 2–30 characters, letters/digits/spaces, not digits-only, no leading digit. A violation is 422 VALIDATION_ERROR, like any other body error.

  • It is resolved through the same chain as topics first, aliases included — so newTopic: "cryptocurrency" simply attaches the existing Cryptocurrency and creates nothing.

  • If an uncurated topic already holds the name, the market is attached to that one instead. Nothing is created, and it does not count against your 3/day.

  • Some names are permanently unavailable because a removed topic once held them. Those return 422 INVALID_TOPIC_NAME — pick a different name.

  • The topic tags this market immediately, but is not addressable by name on your next request until Hilo promotes it, and it will not appear in GET /api/v1/topics. After creating Tennis, for example, using topics: ["Tennis"] on your next market returns 400 until it is promoted.


GET /api/v1/topics

The curated vocabulary, most-used first, plus the alias map. Cached for one hour.

This endpoint is authoritative. The vocabulary is data, not contract — it grows and shrinks as Hilo curates it, and that is not a breaking change.


GET /api/v1/markets/{idOrSlug}

Reads a market by id or slug. Returns title, description, options, current odds, vote counts, topics, image, resolution state and timestamps. 404 MARKET_NOT_FOUND otherwise.

GET /api/v1/me

Your identity, tier, level/XP/HIQ/MIS, today's market count against your limit, the granted scopes, and the key's id, name and prefix.

It also returns canCreateMarket and, when false, requirements[] — the same shape POST /api/v1/markets returns on a 403. Call this before your first create to confirm your account is eligible without a failed write.

POST /api/v1/markets/{idOrSlug}/image

Attaches an image. multipart/form-data, field name file, max 5 MB. A Content-Length header is required — requests without one are rejected with 411. curl -F and mainstream HTTP clients send it automatically.

The format is detected from the file contents, not its name or declared type — a .png renamed to .webp is accepted, a PDF renamed to .webp is not. Images are re-encoded to WebP, EXIF-rotated and resized to fit 1920×1920.

Replacing an existing image requires being the market's creator.


Limits

Market creation volume is governed by your subscription tier, shared with the web app — using both surfaces does not double your allowance:

Tier
Markets per day

Free

1

Premium

5

Premium+

Unlimited

Rate limits on top of that:

Limit
Window

Per key

120 / minute

Per IP

300 / minute

Market creation

5 / minute, 200 / day

Market creation (shared with the web UI)

100 / hour

Image upload

20 / hour

New topic

3 / day

A 429 carries Retry-After in seconds.

Eligibility

Beyond the tier limit, market creation requires a minimum account age, level, MIS score and HIQ rating. These are configured platform-wide and apply to the API exactly as they do to the web UI. A 403 REQUIREMENTS_NOT_MET lists each unmet requirement with your current value and the needed one. GET /api/v1/me gives the same answer without a failed write.


Errors

Every error carries a stable machine-readable code:

Branch on code, never on error. Codes are part of the contract; messages are not and may be reworded at any time.

Code
HTTP
Meaning

MISSING_API_KEY

401

No Authorization header

INVALID_API_KEY

401

Unknown, revoked or expired

INSUFFICIENT_SCOPE

403

Key lacks the required scope

PREMIUM_REQUIRED

403

Subscription lapsed or never held

PREMIUM_PLUS_REQUIRED

403

isMultiSelect needs Premium+

ACCOUNT_DEACTIVATED

403

ACTION_RESTRICTED

403

Account restricted from this action

DAILY_LIMIT_REACHED

403

Tier's daily market limit hit

REQUIREMENTS_NOT_MET

403

See details.requirements

NOT_MARKET_OWNER

403

Only the creator may replace an image

INVALID_OPTION_COUNT

400

TOPIC_NOT_FOUND

400

See details.topics

INVALID_IMAGE

400

Not a decodable image

MARKET_NOT_FOUND

404

USER_NOT_FOUND

404

IDEMPOTENCY_CONFLICT

409

Same key still in flight

PAYLOAD_TOO_LARGE

413

Create body over 1 MB, or image over 5 MB

UNSUPPORTED_MEDIA_TYPE

415

Expected multipart/form-data

INVALID_TOPIC_NAME

422

newTopic name is permanently unavailable (a removed topic holds it)

VALIDATION_ERROR

422

Request body invalid (411 when Content-Length is missing)

RATE_LIMITED

429

See Retry-After

INTERNAL_ERROR

500

STORAGE_ERROR

502

Image storage unavailable

SERVICE_DISABLED

503

API temporarily disabled


Good to know

  1. description is required and used verbatim. Unlike the web UI, nothing writes one for you.

  2. Country fields take full names — "United States", not "US". An unknown value is rejected rather than silently dropped.

  3. Opinion markets always open at equal odds. Seeding your own opening percentages is not available, since it would let you set the odds on your own market.

  4. A topic you mint with newTopic is not resolvable by name on your next request until Hilo curates it.

  5. Photo bonus XP earned via the image endpoint may not be included in the market-created notification's XP total. You still earn it.

  6. One curated topic is unreachable by name — 🏆 2026 World Cup begins with an emoji and does not survive name normalization.


Versioning

  • Additive changes ship in place. New response fields, new optional request fields and new endpoints can appear in v1 at any time. Ignore unknown fields.

  • Breaking changes get /api/v2. Removing or renaming a field, tightening validation, or changing the status code for an existing condition is breaking. Error codes are part of the contract; error messages are not.

  • Deprecation: a removed endpoint keeps serving for 90 days with Deprecation and Sunset headers, and key owners using it get one email.

  • The topic vocabulary is data, not contract.

Not in v1

Drafts, voting and resolution, webhooks, listing your own markets, and AI enrichment of submitted markets.

Last updated

Was this helpful?