DOJO Signal Feed API — Developer Guide

Version 1 · Your base URL comes with your API key. The examples below call it $DOJO_API.

DOJO watches the markets you subscribe to and POSTs a signed JSON signal to your webhook whenever its analysis engine detects a trade setup — entry, stop-loss, and take-profit levels included. You bring a ticker list, a timeframe, and an HTTPS endpoint; DOJO does the rest.


1. Authentication

Every /v1/* request carries your API key as a Bearer token:

Authorization: Bearer <your key>

Keys are issued by the DOJO team and never recoverable — store them in a secret manager. 401 means the key is missing, malformed, or unknown; 403 means the account is suspended.

2. Quick start

curl -X POST $DOJO_API/v1/subscriptions \
  -H "Authorization: Bearer $DOJO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "tickers": ["FOREXCOM:XAUUSD", "BINANCE:BTCUSDT"],
        "timeframe": "5",
        "webhook_url": "https://your-app.example.com/hooks/dojo",
        "webhook_secret": "a-strong-random-string"
      }'

Response — 202 Accepted (provisioning is asynchronous):

{
  "subscription_id": "sub_7f3a1c9e2b40d581",
  "status": "provisioning",
  "timeframe": "5",
  "tickers": {
    "FOREXCOM:XAUUSD": "provisioning",
    "BINANCE:BTCUSDT": "provisioning"
  }
}

Poll GET /v1/subscriptions/{id} until status is active — typically 60–120 seconds per new ticker. From then on, signals arrive at your webhook.

3. Endpoints

POST /v1/subscriptions

Create a subscription.

Field Type Rules
tickers string[] 1–20 per subscription. Format EXCHANGE:SYMBOL, uppercase (e.g. NASDAQ:AAPL). Unknown tickers are rejected per-ticker, not whole-request.
timeframe string One of 1, 5, 15, 60, 240 (minutes) or D (daily). One timeframe per subscription.
webhook_url string HTTPS only. Must resolve to a public address — private, loopback, and link-local hosts are rejected (webhook_host_not_public).
webhook_secret string Optional. When set, every delivery is HMAC-signed (see §5). Strongly recommended.

Whole-request failures return 422 with a detail string. Capacity limits return 429: max 10 live subscriptions per client, and a global resource ceiling (subscribe with fewer tickers or retry later).

Per-ticker statuses you may see: provisioning, active, failed:unknown_ticker, failed:malformed, failed:not_uppercase, failed:missing_exchange_prefix.

GET /v1/subscriptions/{id}

Current state. status is one of:

Status Meaning
provisioning Setup in progress — no signals yet
active Live — signals flow to your webhook
paused Deliveries suspended after repeated webhook failures (§6)
cancelled Terminal — create a new subscription to resume coverage

GET /v1/subscriptions/{id}/deliveries

Your delivery log, newest first (last 50). Each entry:

{
  "delivery_id": "dlv_e7190fc236fdffbd",
  "signal_id": "d728de64c1a4f2e8",
  "status": "delivered",
  "attempt": 1,
  "http_status": 200,
  "last_error": null,
  "payload": { "…the exact body we POSTed…": "…" },
  "delivered_at": "2026-09-01T20:47:28Z",
  "next_retry_at": null,
  "dead_at": null,
  "created_at": "2026-09-01T20:47:26Z"
}

status is delivered, pending (retrying), or dead (gave up — §6).

POST /v1/subscriptions/{id}/resume

Re-activate a paused subscription after you have fixed your endpoint. Returns 200 {"status": "active"}, or 404 if the subscription is not paused. Dead deliveries are history — they are not re-sent.

DELETE /v1/subscriptions/{id}

Cancel. Returns 200 {"status": "cancelled"}. Idempotent-safe: a second call returns 404.

GET /v1/status

Service health plus your own usage:

{
  "status": "operational",
  "degraded_reasons": [],
  "box": { "last_worker_poll_s_ago": 10.8 },
  "resources": { "active": 12, "capacity": 100 },
  "alerts": { "used": 24, "engine_reserve": 20, "budget": 400 },
  "deliveries_24h": { "delivered": 41, "dead": 0, "pending": 1 },
  "subscriptions": { "total": 2, "active": 2, "paused": 0 }
}

degraded means signal detection or delivery may be delayed; already- detected signals are never lost, they deliver late.

4. The signal webhook

When a setup is detected, DOJO POSTs JSON to your webhook_url:

{
  "event": "signal",
  "signal_id": "d728de64c1a4f2e8",
  "subscription_id": "sub_7f3a1c9e2b40d581",
  "ticker": "FOREXCOM:XAUUSD",
  "timeframe": "5",
  "side": "buy",
  "entry": 4428.05,
  "sl": 4425.36,
  "tps": [4430.47, 4432.61, 4434.17],
  "bar_time": "2026-09-01T05:15:00Z",
  "detected_at": "2026-09-01T05:15:04Z"
}

Headers on every delivery:

Header Value
Content-Type application/json
User-Agent dojo-delivery/1.0
X-Dojo-Delivery Unique id per delivery attempt
X-Dojo-Signature sha256=<hex> — only when your subscription has a webhook_secret (§5)

Respond with any 2xx within 10 seconds. Do your processing async — acknowledge first, act after.

5. Verifying signatures

The signature is an HMAC-SHA256 of the raw request body with your webhook_secret. Always compare with a constant-time function.

Python:

import hashlib, hmac

def verify(raw_body: bytes, header_value: str, secret: str) -> bool:
    expect = "sha256=" + hmac.new(secret.encode(), raw_body,
                                  hashlib.sha256).hexdigest()
    return hmac.compare_digest(header_value, expect)

Node:

const crypto = require("crypto");

function verify(rawBody, headerValue, secret) {
  const expect = "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(headerValue),
                                Buffer.from(expect));
}

Verify against the raw bytes — re-serializing the parsed JSON will change key order and break the digest.

6. Delivery semantics — read this twice

7. Error reference

Code Where Meaning
401 any /v1/* Missing/malformed/unknown API key
403 any /v1/* Client suspended
404 subscription routes Not yours, unknown, or wrong state (e.g. resume on a non-paused sub)
422 POST /v1/subscriptions Whole-request validation failure — detail says which field
429 POST /v1/subscriptions Subscription or capacity limit reached

Per-ticker problems never fail the request — check the tickers map.

8. Limits (v1)

Limit Value
Tickers per subscription 20
Live subscriptions per client 10
Timeframes 1, 5, 15, 60, 240, D
Delivery attempts 5, then dead-letter
Webhook response window 10 s

Questions or a key request: sensei@clawback.capital.