Risk LabsProposer API

Batch proposing

Prepare, submit, and track up to 100 independent proposals per request.

The Proposer API supports batch proposing at https://api.risklabs.co. Use POST endpoints to submit independently signed proposals and poll their candidate statuses, with an optional helper to fetch signing context. Each request accepts 1–100 items, subject to the shared rate-limit budget. This is a request-size cap, not a throughput quota.

StepEndpointRequest body
Prepare (optional)/propose/batch/signing-data{ "identifiers": ["question-id-or-slug"] }
Submit/propose/batch{ "proposals": [signedProposal, ...] }
Track/propose/batch/status{ "candidate_ids": ["candidate-uuid"] }

Send Content-Type: application/json. Use these exact paths, without a trailing slash. All three are available through the same gateway as the single-proposal v1 endpoints.

Preparation is strictly optional. If you already have the current request details, bond requirements, and trusted signing configuration, build and sign each Permit2 witness locally and go straight to submission. The API does not require a prior /propose/batch/signing-data call or any preparation token.

How a batch behaves

Each proposal has its own authorization, candidate ID, and outcome. The API processes signing-context and admission items with bounded concurrency and returns results in input order. One item can succeed while another fails; there is no rollback of successful items when a sibling fails.

A completed batch call returns HTTP 200, even when some or all items failed. Read every item's status_code and error. Request-level failures, such as malformed input, more than 100 items, rate limiting, or unavailable service, can instead return a non-200 response without per-item results. Schema validation applies to the whole request before items are processed.

Batching combines HTTP requests. It does not create one shared signature, a durable batch ID, an atomic group of candidates, or a guarantee that candidates will execute in the same Polygon transaction. Admission is asynchronous: acceptance does not mean the proposed answer is correct, selected, or on-chain.

Prepare signing context

POST https://api.risklabs.co/propose/batch/signing-data

{
  "identifiers": ["example-market-a", "example-market-b"]
}

Use real canonical question IDs or market slugs in place of the examples. For each successful item, results[i] contains the original identifier, status_code: 200, and signing_data; a failed item contains an error instead. The signing context provides:

FieldPurpose
question_id, requestCanonical question, oracle, requester, oracle identifier, timestamp, and ancillary bytes
price.allowed_valuesAllowed numerical proposal prices
bond.token, bond.required_bondBond token and required amount in raw token units
contracts.permit2, contracts.signed_proposerSigning domain contract and authorized spender
signingChain ID, maximum payment, and witness type definitions

Verify the request and contracts against trusted configuration. Choose an allowed price, ensure enough bond balance and Permit2 allowance, and allocate a distinct unused Permit2 nonce for every authorization. Reserve funds across all proposals that could execute; preparation does not reserve balance, allowance, or nonces.

Sign each proposal

The optional batch preparation helper returns signing context, not the complete v1 typed_data response. Construct the Permit2 witness structure for each proposal. The fields below refer to the helper response; if you skip it, supply the equivalent current values from your own request data and trusted configuration:

  • Domain: Permit2, chain signing.chain_id (Polygon 137), verifying contract contracts.permit2, with no domain version.
  • Message: token bond.token, your chosen permit amount, unused nonce and deadline, and spender contracts.signed_proposer.
  • Witness: the returned request's oracle, requester, identifier.raw, timestamp, and ancillary bytes, plus your chosen proposedPrice and maxPayment: 0.

The witness's oracle identifier is distinct from the question ID. Use bigint for Solidity integers in your signer, preserve exact ancillary bytes, and sign each message independently. Check wallet eligibility before submitting.

Submit signed proposals

POST https://api.risklabs.co/propose/batch

The following illustrates one item; add up to 100 to proposals. Replace the example identifier, wallet, signature, nonce, amount, and deadline with verified values. The timestamp is illustrative; choose a short deadline at least five minutes beyond admission.

{
  "proposals": [
    {
      "identifier": "example-market-a",
      "client_ref": "job-1",
      "proposer": "0x1111111111111111111111111111111111111111",
      "proposed_price": "1000000000000000000",
      "max_payment": "0",
      "permit": {
        "amount": "10500000",
        "nonce": "7",
        "deadline": 1800000000
      },
      "signature": "0x..."
    }
  ]
}

Save each exact signed payload before submitting. client_ref is an optional caller-supplied correlation string, echoed in the result; it is not an idempotency key. Use the returned canonical question ID as identifier where possible.

Batch admission uses the existing compatibility format: proposed_price is a numerical price string, permit.deadline is a JSON integer in Unix seconds, and there is no version object or outcome field. permit.amount and permit.nonce remain decimal strings. Do not send a v1 admission payload unchanged.

The response has results and summary (accepted, replayed, rejected). Each result includes client_ref, status_code, and either candidate or error:

Item statusMeaningNext step
202New candidate durably accepted for asynchronous validationSave candidate.candidate_id and poll
200Exact signed-payload replay within the current request round; existing candidate returnedFollow that candidate
OtherThis item failed; inspect error.code and error.detailHandle that item's error before retrying

A successful candidate contains candidate_id, status, received_at, and status_url. For gateway batch clients, poll the batch status endpoint below. The compatibility status_url points to /propose/{question_id}/status/{candidate_id}, a single-item route that is not exposed through this gateway.

Track candidates

POST https://api.risklabs.co/propose/batch/status

{
  "candidate_ids": ["11111111-1111-4111-8111-111111111111"]
}

Replace the example UUID with a returned candidate ID. Each result contains candidate_id, status_code, and either candidate or error. Unknown candidates produce per-item 404 results without failing other lookups.

Follow the candidate's status and its separate, nullable submission. A selected candidate has been chosen for execution; a submission marked confirmed or indexed has reached chain. Continue tracking oracle liveness, disputes, and settlement separately. Batch statuses include nullable failure details (code and detail), but omit v1's richer recovery fields such as next_action. If you need that guidance, use the exposed GET /v1/proposals/{question_id}/status/{candidate_id} route with the matching question ID.

Limits and retries

Split more than 100 items into separate requests. Preparation, submission, and each polling pass all charge by item count against the same budget; eligibility and single-proposal calls also share that budget. Preparing 100 questions, submitting 100 candidates, and polling them once costs 300 items, not 100. Skipping the optional preparation call saves its 100-item charge; submission plus one polling pass still costs 200 items and remains subject to the shared budget.

The backend defaults are 120 items per IP per 60-second window and 1,000 items globally per window; operator configuration can differ. The gateway does not forward a trusted end-user IP, so clients seen under the same proxy identity share the upstream IP budget. Do not assume an independent allowance for each wallet or API caller.

With those defaults, preparing 100 items and immediately submitting 100 exceeds the IP quota and the submission returns 429 before admission. Pace phases across quota windows and budget for every polling pass. Splitting a batch or adding concurrency does not reduce its total item cost. Rejected rate-limited attempts also charge the counters, so avoid tight retry loops. Honor Retry-After when supplied or the retry delay in the error detail, and use backoff with jitter. Coordinate higher-throughput capacity with the integration team; do not assume the 100-item cap reserves quota or guarantees completion in one window.

Keep concurrent calls bounded and reserve collateral across requests. Request and response size limits can also require smaller batches, especially with large signatures or ancillary data; a gateway 413 means the request body is too large.

An HTTP 200 only means you received per-item outcomes. Handle known item errors individually. For transient failures, retry the affected items with their original signed payloads after backoff. If a response is lost, times out, or returns a gateway 502/504, some candidates may already have been stored. Within the current request round, replay the exact saved items to recover their acknowledgements. Do not generate new nonces merely because a response was uncertain.

Replay lookup does not recover candidates from an earlier oracle request round. If the indexed round has advanced, stop replaying the old signature and reconcile the earlier candidate before preparing a new authorization. Poll its saved candidate ID if available; if the lost response left you without one, contact the integration team with the question ID, client_ref, and submission time, never the signature or private key. A new round requires fresh signing context. See status and retries for the shared lifecycle and recovery principles.

On this page