Documentation Index
Fetch the complete documentation index at: https://docs.b1e55ed.permanentupperclass.com/llms.txt
Use this file to discover all available pages before exploring further.
SPI Interface Specification
Standard Producer Interface v1 — Formal Contract
Protocol version: spi/v1
Status: Normative
Stability: Stable (additions only — no breaking changes without major version bump)
Agent-First Interface Summary
For LLM agents and automated systems, the minimal interface is:
# SPI — Standard Producer Interface v1
Submit signals: POST /api/v1/spi/signals
Required headers: X-Producer-Key: spi_key_*
Body: {"symbol": "BTC-USD", "direction": "bullish|bearish|neutral", "confidence": 0.55-0.99, "horizon_hours": 24-720}
Check karma: GET /api/v1/spi/producers/{producer_id}/karma
Required headers: X-Producer-Key: spi_key_*
1. Signal Payload Schema
1.1 Submission Request (JSON Schema)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://b1e55ed.permanentupperclass.com/schemas/spi/v1/signal-submit.json",
"title": "SPI Signal Submission",
"description": "Directional market signal submitted by an authenticated producer",
"type": "object",
"required": ["symbol", "direction", "confidence", "horizon_hours"],
"additionalProperties": false,
"properties": {
"symbol": {
"type": "string",
"description": "Asset identifier. Format: BASE-QUOTE (e.g. BTC-USD, ETH-USD, SOL-USD)",
"pattern": "^[A-Z0-9]+-[A-Z0-9]+$",
"examples": ["BTC-USD", "ETH-USD", "SOL-USD", "WIF-USD"]
},
"direction": {
"type": "string",
"enum": ["bullish", "bearish", "neutral"],
"description": "Directional assertion for the symbol within the horizon window"
},
"confidence": {
"type": "number",
"minimum": 0.55,
"maximum": 0.99,
"description": "Producer's probability estimate that direction is correct. P(direction | horizon). Must be in [0.55, 0.99]."
},
"horizon_hours": {
"type": "integer",
"minimum": 24,
"maximum": 720,
"description": "Forecast horizon in hours. The attribution window closes this many hours after submission.",
"examples": [24, 48, 72, 168, 336, 720]
},
"client_signal_id": {
"type": "string",
"maxLength": 255,
"description": "Producer-side idempotency key. If provided, duplicate submissions with the same client_signal_id return 409 without creating a new record. Strongly recommended.",
"examples": ["my-signal-2026-03-16-btc-001", "550e8400-e29b-41d4-a716-446655440000"]
}
}
}
1.2 Field Constraints Summary
| Field | Type | Required | Min | Max | Notes |
|---|
symbol | string | ✅ | 3 chars | 20 chars | BASE-QUOTE format |
direction | enum | ✅ | — | — | bullish, bearish, neutral only |
confidence | float | ✅ | 0.55 | 0.99 | P(correct) — not a weight |
horizon_hours | int | ✅ | 24 | 720 | Hours until attribution window closes |
client_signal_id | string | ❌ | — | 255 chars | Idempotency key; auto-generated if omitted |
1.3 Submission Acknowledgment (Response)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://b1e55ed.permanentupperclass.com/schemas/spi/v1/signal-ack.json",
"title": "SPI Signal Acknowledgment",
"type": "object",
"required": ["signal_id", "status", "attribution_window_end"],
"properties": {
"signal_id": {
"type": "string",
"format": "uuid",
"description": "Canonical internal signal ID assigned by the gateway"
},
"status": {
"type": "string",
"enum": ["accepted", "duplicate"],
"description": "'accepted' for new signals (201); 'duplicate' when client_signal_id was already recorded (409)"
},
"attribution_window_end": {
"type": "string",
"format": "date-time",
"description": "ISO-8601 timestamp when the forecast window closes and resolution begins"
}
}
}
2. Outcome Resolution Rules
2.1 Resolution Trigger
Resolution is attempted after attribution_window_end passes. The resolver checks whether a price feed is available for the symbol. If no price data is available within the grace period (default: 4 hours post-window-close), the signal is marked unresolvable and excluded from karma computation.
2.2 Direction Correctness
Resolution compares entry_price (price snapshot at admission time) to exit_price (TWAP price at window close):
| Submitted direction | Condition | Outcome |
|---|
bullish | exit_price > entry_price | Correct |
bullish | exit_price ≤ entry_price | Incorrect |
bearish | exit_price < entry_price | Correct |
bearish | exit_price ≥ entry_price | Incorrect |
neutral | |Δprice/entry_price| < threshold (default 2%) | Correct |
neutral | |Δprice/entry_price| ≥ threshold | Incorrect |
2.3 Brier Score Computation
The Brier score measures forecast calibration. For a binary event (direction correct = 1, incorrect = 0):
brier_score = (forecast_probability - actual_outcome)²
Where:
forecast_probability = the submitted confidence value
actual_outcome = 1.0 if direction was correct, 0.0 if incorrect
Score range: [0.0, 1.0]. Lower is better.
- Perfect calibrated correct call at 0.80 confidence:
(0.80 - 1.0)² = 0.04
- Perfectly calibrated incorrect call at 0.80 confidence:
(0.80 - 0.0)² = 0.64
- Coin-flip baseline:
0.25
Why this penalizes overconfidence: A wrong call at 0.95 scores (0.95)² = 0.90 — nearly maximum penalty. A wrong call at 0.60 scores (0.60)² = 0.36 — much less damage.
2.4 Karma Ledger Spec
Epoch definition: 7 days (UTC week boundaries, Monday 00:00 UTC → Sunday 23:59 UTC).
Initial karma: 0.50 for all new producers.
Per-epoch karma update:
epoch_karma = 1 - brier_score # per resolved signal; averaged across epoch
new_karma = clip((0.70 × old_karma) + (0.30 × epoch_karma), 0.0, 1.0)
Smoothing factor: 0.70 (EMA — retains 70% of prior karma per epoch, preventing a single epoch from dominating).
Interpretation:
- Perfect epoch (all correct, well-calibrated):
epoch_karma ≈ 1.0 → karma rises
- Coin-flip epoch:
epoch_karma ≈ 0.75 → karma roughly unchanged
- Poor epoch (mostly wrong or overconfident):
epoch_karma low → karma falls
Thresholds:
| Threshold | Value | Effect |
|---|
| Shadow activation | 5 signals submitted | Karma tracking begins |
| Active activation | karma ≥ 0.55 + 10 resolved signals | Promoted to active |
| Suspend trigger | karma < 0.30 for 3 consecutive epochs | Auto-suspend |
| Auto-suspend floor | karma = 0.30 | Hard threshold |
3. Idempotency Contract
3.1 client_signal_id Behavior
If a submission includes a client_signal_id:
- The gateway hashes the combination
(producer_id, client_signal_id) as a deduplication key.
- On first submission: the signal is created, stored, and
201 Created is returned.
- On subsequent submissions with the same
client_signal_id:
409 Conflict is returned
- The response body contains the original
signal_id and status: "duplicate"
- No new signal record is created
- No karma impact from the duplicate
Contract: A 409 response is NOT an error. It means “your signal was already accepted.” Treat it identically to a 201 — the signal exists in the system.
3.2 Auto-Generated IDs
If client_signal_id is omitted, the gateway generates a UUID internally. In this case, duplicate HTTP requests create duplicate signal records. Use client_signal_id to prevent this.
3.3 Idempotency Window
Idempotency records are permanent (no expiry). A client_signal_id submitted in epoch 1 will still return 409 in epoch 100.
4. Error Taxonomy
All error responses follow this envelope:
{
"code": "spi.error_code",
"message": "Human-readable description",
"status": 400
}
| HTTP Status | Code | When |
|---|
201 | — | Signal accepted (new) |
400 | spi.invalid_signal | Direction not in {bullish,bearish,neutral}, confidence out of range, symbol malformed |
403 | spi.missing_key | X-Producer-Key header absent |
403 | spi.unknown_key | Key doesn’t match any registered producer |
403 | spi.suspended | Producer account is suspended |
409 | — | Duplicate client_signal_id — idempotent, contains original signal_id |
422 | — | Missing required fields (Pydantic validation failure) |
429 | spi.quota_exceeded | Hourly quota exceeded (default: 100 signals/hour) |
500 | spi.internal_error | Gateway-side fault — safe to retry with exponential backoff |
Retry Policy
| Status | Retry? | Notes |
|---|
201 | No | Already accepted |
400 | No | Fix the payload |
403 | No | Fix credentials or contact operator |
409 | No | Already accepted — treat as success |
422 | No | Fix the payload |
429 | Yes, after 60s | Back off; don’t burst |
500 | Yes, with backoff | Transient fault — exponential backoff recommended |
5. Authentication
All SPI API keys are prefixed with spi_key_ followed by 64 hex characters:
spi_key_a1b2c3d4... (total: 72 characters)
Keys are stored as SHA-256 hashes server-side. The plaintext key is shown exactly once at registration and cannot be recovered.
X-Producer-Key: spi_key_your_key_here
The header name is case-insensitive in HTTP/2 but the canonical form uses this casing.
5.3 Key Rotation
If a key is compromised, contact the operator for manual rotation. Automated rotation is a v1.1 feature.
6. Versioning
6.1 Protocol Version
Current version: spi/v1
All endpoints are mounted under /api/v1/. Future breaking changes will increment to /api/v2/.
6.2 Compatibility Policy
- Additive changes (new optional fields, new endpoints): No version bump. Backward compatible.
- Breaking changes (removed fields, changed semantics): Major version bump. Both versions maintained for ≥ 6 months.
- Schema version field: Signal payloads MAY include
"schema_version": "spi.v1". Currently informational; will be required if/when v2 is deployed alongside v1.
6.3 Forward Compatibility
Gateways MUST ignore unknown fields in submission bodies. Producers MUST ignore unknown fields in response bodies. This allows both sides to evolve independently within a major version.
7. Rate Limits and Quotas
| Lifecycle State | Hourly Signal Quota | Burst (per minute) |
|---|
onboarding | 10 | 5 |
shadow | 50 | 20 |
active | 100 | 50 |
suspended | 0 | 0 |
Quota violations return 429 spi.quota_exceeded. Repeated quota violations trigger the spam slash condition and accelerate suspension.
8. Symbol Registry
The SPI accepts any BASE-QUOTE symbol string. However, signals on illiquid or unsupported symbols may be marked unresolvable if no price feed is available at resolution time. This does not penalize karma — the signal is simply excluded from scoring.
Commonly supported symbols: BTC-USD, ETH-USD, SOL-USD, WIF-USD, BONK-USD, JUP-USD.
Check with the operator for the current supported symbol list.
9. Signal Lifecycle
submitted → validated → accepted → [window_open] → [window_close] → resolving → resolved
↓ ↓
rejected unresolvable
| State | Description |
|---|
submitted | Received by gateway, pending validation |
validated | Passed schema and semantic validation |
accepted | Written to spi_signals, idempotency key stored |
window_open | Attribution window active — no changes allowed |
resolving | Window closed, resolver fetching price data |
resolved | Brier score computed, karma delta applied |
rejected | Failed validation (never stored in main ledger) |
unresolvable | No price data available — excluded from scoring |