Skip to content

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.

limit responses
# 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

shell
$ 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.

publish.py
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.

subscribe.py
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.

rate_limit.py
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.

Getting started

API reference

The full REST surface — publish, catch-up, deliveries, replay, endpoints, entitlements — as interactive OpenAPI 3.1.

Open the reference