Common External API Contract
This document is the canonical wire contract shared by every external HeyTraders API domain. Read it before using a domain-specific document.
Connection
| Item | Contract |
|---|---|
| Base URL | https://api.hey-traders.com/v1 |
| OpenAPI | https://api.hey-traders.com/v1/openapi.json |
| Health | GET https://api.hey-traders.com/v1/meta/health |
| API-key header | X-API-Key: <api-key> |
| Default request/response media type | application/json |
Build endpoint URLs by appending paths such as /market/ohlcv to the Base URL.
The removed /api/v1 prefix is not supported. The website's same-origin proxy
is a first-party browser path and is not the external agent contract. Public
documentation endpoints can also return text/markdown when requested.
Authentication
External agents authenticate with exactly one header:
X-API-Key: <api-key>
Do not send an API key as Authorization: Bearer. Bearer tokens on the shared
/v1 surface are reserved for first-party browser sessions. Public endpoints
such as health and documentation do not require a credential.
API keys are stored by SHA-256 hash. A raw value created from authenticated HeyTraders settings is shown once; store it as a secret and never place it in prompts, logs, source control, URLs, or payment payloads.
An invalid or inactive key returns HTTP 401 with UNAUTHORIZED. If the key
store is unavailable, the API returns HTTP 503 with AUTH_UNAVAILABLE; keep
the current key and retry instead of registering or rotating credentials.
Response Contract
An HTTP 2xx response is the endpoint payload itself. There is no common success envelope:
{
"backtest_id": "abc-123",
"status": "queued"
}
If a domain payload has its own success field, that field is business data;
do not treat it as a transport envelope. Use the HTTP status to decide whether
the request succeeded.
Every HTTP 4xx or 5xx response uses this shape:
{
"type": "error",
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"message": "This endpoint requires 'trade' permission",
"suggestion": "Update this user-owned API key's permissions in HeyTraders settings.",
"details": {
"required": "trade",
"granted": ["research", "read"]
}
}
}
error.code and error.message are always present. error.suggestion and
error.details are optional. Validation errors use HTTP 422 and may put a list
of field errors in error.details.
Clients should:
- Branch on HTTP status.
- On error, read
error.codebeforeerror.message. - Follow
error.suggestiononly after checking whether the proposed action is safe and authorized. - Honor
Retry-After,X-RateLimit-*,X-Payment-Required,PAYMENT-REQUIRED, andPAYMENT-RESPONSEheaders.
API-key provisioning
Public self-registration and browser claim-code handoff are not part of the current contract. An external HTTP client must start with a user-owned API key created from an authenticated HeyTraders account. The raw key is shown only at creation time.
OpenClaw does not use this API-key bootstrap. Its HeyTraders plugin creates or resumes an Agent-owned browser session with proof of possession, and the live browser command catalog remains the only model-facing application boundary.
For a pre-existing API key that has a legacy Agent profile, read
GET /v1/meta/agents/me and treat its scopes, tier, and quota as the
effective state. AGENT_NOT_FOUND means that key has no legacy Agent profile;
it does not authorize creating one through a public endpoint.
Permission Scopes
| Scope | Capability |
|---|---|
research | Public research, market data, strategy authoring, and backtests |
read | Read the key owner's eligible account and portfolio data |
trade | Submit trading mutations allowed by the owned account and policy |
Rules:
- A user-owned key receives its permissions when it is created or updated in authenticated HeyTraders settings.
- Historical provisional keys remain
researchonly and cannot be linked or upgraded through a public endpoint. researchis always included.tradeis valid only whenreadis also granted.- A scope does not guarantee exchange connection, balance, trade eligibility, or live-strategy availability. Check the target domain's capability endpoint.
Rate Limits and Quota
Rate limits protect request volume; quotas limit resource consumption. They are separate contracts.
- Authenticated API keys currently have a 300 requests/minute protection limit.
- Public and computationally heavy endpoints can have additional IP buckets.
- Historical provisional-key limits remain server-owned and can change.
- User plan limits are loaded from billing configuration and can change.
Browser sessions and all user-owned API keys for the same user share one
backtest/live quota identity.
GET /v1/meta/agents/mereturns that shared quota usage together with the current key's request-rate usage. - Day and week backtest windows use UTC; live strategy quota is concurrent.
- A
limitofnullmeans the key is exempt from that resource-usage quota. The 300 requests/minute server-protection limit still applies.
Example profile quota fragment:
{
"tier": "free",
"quota": {
"requests_minute": {"used": 8, "limit": 300},
"backtests_day": {"used": 2, "limit": 100},
"backtests_week": {"used": 7, "limit": 500},
"live_strategies": {"used": 1, "limit": 3}
}
}
quota_used on an agent profile is informational product telemetry. The
quota object and structured quota errors are the enforcement contract.
Quota and rate failures return HTTP 429 with one of these codes:
| Code | Meaning |
|---|---|
RATE_LIMITED | Request-volume limit reached |
FREE_QUOTA_EXCEEDED | Historical provisional-key quota reached; use a user-owned key |
QUOTA_EXCEEDED | Current user plan quota reached |
If the distributed usage store cannot safely read or enforce these counters,
the API fails closed with HTTP 503 and RATE_LIMIT_UNAVAILABLE or
QUOTA_UNAVAILABLE. Keep the current key and retry later.
Read error.details for current usage, limit, window, and retry timing.
x402 Payment
x402 is an experimental upgrade path, not a way to bypass ownership or permission checks.
Eligibility is strict:
- The key must be a user-owned legacy Agent key. The persisted compatibility
marker is currently
provisioning_type=agent_claimed. - The key must be linked to a non-empty user ID.
- The x402 facilitator and billing activation service must both be ready.
- The challenge offers only plans above the current tier that can admit the next request at the key's current usage.
A provisional or unowned key that submits X-Payment receives HTTP 403 with
X402_CLAIM_REQUIRED. The code name is retained for wire compatibility;
HeyTraders does not verify or settle that proof.
When an eligible user-owned key exhausts a quota and x402 is available, the API
returns HTTP 402, X-Payment-Required: true, and:
{
"type": "error",
"error": {
"code": "PAYMENT_REQUIRED",
"message": "Quota exceeded. Pay via x402 to upgrade your subscription.",
"suggestion": "Submit a new proof in X-Payment, then retry this exact resource.",
"details": {
"quota": {
"current_usage": 20,
"limit": 20,
"window": "24 hours (UTC)",
"retry_after": 3600,
"resource": "backtest"
},
"payment": {
"x402Version": 1,
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"maxAmountRequired": "39000000",
"resource": "https://api.hey-traders.com/v1/backtest/execute",
"description": "HeyTraders Pro ($39/mo launch price) - 30 days"
}
]
}
}
}
}
The example is illustrative. The actual accepts entries in the response are
authoritative for network, asset, amount, recipient, and resource. The same
challenge is encoded in the standard PAYMENT-REQUIRED response header so an
x402 SDK can parse it even though the JSON body keeps HeyTraders' common error
shape.
Use the challenge at error.details.payment to create a proof for the exact
resource URL, including its query string. Submit the new proof as:
X-Payment: <base64url-payment-proof>
Never reuse a proof. Important failure codes are:
| Code | HTTP | Required action |
|---|---|---|
X402_CLAIM_REQUIRED | 403 | Use a user-owned key; the submitted proof was not settled |
PAYMENT_REQUIRED | 402 | Create a new proof from error.details.payment |
PAYMENT_REJECTED | 402 | Discard the proof and fetch a fresh challenge |
PAYMENT_NOT_REQUIRED | 409 | Retry without X-Payment; no settlement was attempted |
X402_UPGRADE_UNAVAILABLE | 409 | No higher x402 plan can admit the request; discard the proof |
PAYMENT_UNAVAILABLE | 503 | Discard an unsettled proof; if settlement is unknown, never reuse it and keep support_ref |
ACTIVATION_FAILED | 500 | Payment settled but activation failed; keep support_ref and contact support |
The server rejects same-tier payments and downgrades before acquiring the proof lock or contacting the facilitator. If no higher plan has enough quota headroom, the quota response remains HTTP 429 and contains no payment challenge.
When PAYMENT_UNAVAILABLE includes
error.details.settlement_state = "unknown", the transfer may have completed.
Do not retry that proof. Keep error.details.support_ref and contact support.
After a successful paid request, fetch GET /v1/meta/agents/me and verify the
effective tier and quota rather than assuming settlement alone activated the
plan. Successful settlement also returns the SDK-standard PAYMENT-RESPONSE
header. A settlement attempt that returns PAYMENT_REJECTED may include that
header as well; inspect its decoded failure result before creating a fresh
proof.
Documentation Discovery
Documentation endpoints are public and follow the same bare-success contract:
GET /v1/docs
GET /v1/docs/common
GET /v1/docs/api-reference
POST /v1/docs/context
GET /v1/openapi.json
Use GET /v1/docs/common for this contract, GET /v1/docs to discover domain
topics, and POST /v1/docs/context when only selected canonical sections are
needed. Request Accept: text/markdown for markdown or
Accept: application/json for the structured document representation.
Every OpenAPI operation contains security. An empty array means the operation
is public; [{"ApiKeyAuth": []}] means the current key is required in
X-API-Key. x-heytraders-required-scopes, when present, lists the additional
capability scopes enforced by that operation. A missing security field is a
contract publication error and callers must not infer authentication from the
path or HTTP method.