Docs
Sandbox
The sandbox is a free 7-day evaluation tenant on the production relay: the full API surface, all three transports — WebSocket, webhook, and REST pull — synthetic data only, no SLA. It exists so your engineers can prove the integration end to end before anything commercial is signed.
Access is granted by the MultiEdge team: request sandbox access. You receive a tenant and an admin API key (mesk_..., shown once) — keys are sent as Authorization: Bearer.
Limits
Sandbox limits are fixed and enforced by the API — they are sized for integration engineering, not for load testing or distribution.
1
strategy
2
subscribers
60
published signals / minute
7 days
then the tenant expires
The tenant expires 7 days after grant. On expiry every API key answers HTTP 403 — contact us to extend the evaluation or move to production. When the publish budget is exhausted the API answers 429 with a Retry-After header; the budget refills each minute.
# After expiry — every API key on the tenant answers:
HTTP/1.1 403 Forbidden
{"error": "forbidden", "reason": "plan_expired"}
# When the publish budget is exhausted — the API answers:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{"error": "plan_rate_limited", "retry_after_seconds": 12} Synthetic data only
Publish test and synthetic signals only — never production strategy output. The sandbox is for integration engineering, not distribution: no entitlement, KYB, or commercial terms are in place for real signal content, and nothing published there should reach a downstream consumer. Production distribution starts with a production tenant.
1 · Install
$ uv add multiedge-relay 2 · Publish a synthetic signal
Set MULTIEDGE_API_KEY to the sandbox key you were issued. The client_signal_id is your idempotency key: retries return the original ack instead of publishing twice, in the sandbox exactly as in production.
import os
from multiedge_relay import Signal, SignalPublisher
signal = Signal(
strategy_id="str_sandbox_demo",
client_signal_id="2026-08-21-demo-001", # idempotency key — safe to retry
schema_version="portfolio_rebalance/1.1",
payload={ # synthetic — never production output
"kind": "portfolio_rebalance", # one signal = the WHOLE portfolio:
"signal_date": "2026-08-21", # an absent ticker is liquidated
"planned_execution_date": "2026-08-22",
"positions": [
{"ticker": "TEST_A", "action": "BUY", "signal_portfolio_weight": 0.5},
{"ticker": "TEST_B", "action": "SELL", "signal_portfolio_weight": 0.3},
{"ticker": "TEST_C", "action": "HOLD", "signal_portfolio_weight": 0.2},
],
},
)
with SignalPublisher(api_key=os.environ["MULTIEDGE_API_KEY"]) as publisher:
ack = publisher.publish(signal)
print(ack.sequence) # gapless per-strategy sequence — same contract as production 3 · Subscribe with catch-up
The SDK keeps a durable cursor: on start it gap-fills over REST from that cursor, dedupes the overlap with the live stream by sequence, and only then goes live. Stop the subscriber, publish a few signals, and restart it — watching the catch-up close the gap is the core of the evaluation.
import os
from multiedge_relay import SignalSubscriber
def handle(signal, meta):
print(signal.sequence, signal.payload) # live AND caught-up signals, in order
sub = SignalSubscriber(api_key=os.environ["MULTIEDGE_API_KEY"],
strategy_id="str_sandbox_demo",
on_signal=handle)
sub.run() # resumes from the persisted cursor, gap-fills, then goes live 4 · Handle the publish budget
You do not need machinery for the 60/min budget — the SDK's publisher retries transient errors, including 429, on its own. If a sustained burst exhausts the retry budget too, the publisher raises multiedge_relay.exceptions.PublishFailed rather than dropping the signal; the honest handling is to wait and resend the same signal.
import time
from multiedge_relay import PublishFailed
# The publisher already retries transient errors (408, 429, 5xx) with backoff.
# PublishFailed means that budget ran out — in the sandbox, usually a sustained
# burst past 60 signals/min. Wait out the window and resend the SAME signal:
# the relay dedupes on client_signal_id, so the retry cannot double-publish.
try:
ack = publisher.publish(signal)
except PublishFailed:
time.sleep(60) # the sandbox budget is per minute
ack = publisher.publish(signal) # duplicate-safe: same client_signal_id Moving to production
Production tenants are provisioned by the team — contact us when the integration is proven. It is the same API and the same SDK: your integration code is unchanged, only the key changes. Tiers and anchor prices are on the pricing page.
Getting started
The full SDK walkthrough — publish, subscribe with catch-up, exactly-once processing, and webhook signature verification.
API reference
The full REST surface — publish, catch-up, deliveries, replay, endpoints, entitlements — as interactive OpenAPI 3.1.