Skip to content
OpenRelay is in early access, and the /v1 API is stable. New capabilities ship in the changelog.
Inference APIBatch API

Results

The output and error file formats. One JSONL line per request, joined back to your inputs by custom_id.

When a batch reaches completed (or expired with partial work), it produces up to two result files, referenced on the batch object:

  • output_file_id: successful requests, one line each.
  • error_file_id: failed requests, one line each. Present only if at least one request failed.

Download either with the Files content endpoint:

curl https://inference.openrelay.inc/v1/files/file-batch_9f8e7d6c5b4a-output/content \
  -H "Authorization: Bearer $OPENRELAY_API_KEY" \
  -o results.jsonl

CLI: orl files content get file-batch_9f8e7d6c5b4a-output -o results.jsonl.

Result line shape

Every line in both files is one result object, matching the OpenAI batch output shape:

FieldTypeDescription
idstringA per-result id (batch_req_<custom_id>).
custom_idstringThe custom_id from the matching input request. Use it to join results back to inputs.
responseobject or nullOn success: { status_code, body }, where body is the full endpoint response (e.g. a chat completion). Null on failure.
errorobject or nullOn failure: { code, message, status_code, body }. Null on success.

Exactly one of response and error is set on each line. The result files are not ordered to match the input file; always join by custom_id.

Output file line (success)

{
  "id": "batch_req_req-1",
  "custom_id": "req-1",
  "response": {
    "status_code": 200,
    "body": {
      "id": "chatcmpl-abc123",
      "object": "chat.completion",
      "model": "openrelay/gpt-oss-120b",
      "choices": [
        { "index": 0, "message": { "role": "assistant", "content": "Red, blue, and yellow." }, "finish_reason": "stop" }
      ],
      "usage": { "prompt_tokens": 17, "completion_tokens": 8, "total_tokens": 25 }
    }
  },
  "error": null
}

Output file line, tool-calling record

A record submitted with openrelay.tool_config whose model actually tool-called carries one extra field in response.body:

{
  "id": "batch_req_req-2",
  "custom_id": "req-2",
  "response": {
    "status_code": 200,
    "body": {
      "id": "chatcmpl-def456",
      "object": "chat.completion",
      "model": "openrelay/gpt-oss-120b",
      "choices": [
        { "index": 0, "message": { "role": "assistant", "content": "The invoice total is $4,210." }, "finish_reason": "stop" }
      ],
      "usage": { "prompt_tokens": 2140, "completion_tokens": 96, "total_tokens": 2236 },
      "openrelay": { "tool_rounds": 3 }
    }
  },
  "error": null
}
FieldTypeDescription
openrelayobjectNamespace for OpenRelay response extensions. Ignore any key you do not recognize: this object is additive, and new keys may appear without a breaking change. The request side uses the same namespace: body.openrelay.tool_config is where you ask for tool calling.
openrelay.tool_roundsintegerHow many tool round-trips the worker ran for this record, so the answer came from tool_rounds + 1 model turns.

Where it appears:

  • Only on output lines, inside response.body, and only when at least one tool round ran. An openrelay.tool_config record whose model answered immediately is byte-for-byte identical to a record with no tool_config at all.
  • Never on error lines, and never on a record without openrelay.tool_config.
  • Alongside a usage object that is summed across every turn of the record, which is what you are billed for. tool_rounds is how you tell a legitimately expensive agentic record from an anomalous one.

The field is on the response body only. openrelay.tool_config itself is stripped before every model invocation and never appears in either result file, and an openrelay object left empty by that strip is removed with it.

Request and response now share one namespace word: you ask under body.openrelay, and you read the answer's metadata under response.body.openrelay. A record still carrying the retired bare top-level tool_config fails with tool_config_moved; see the migration note in Tool calling in a batch.

Error file line (failure)

{
  "id": "batch_req_req-7",
  "custom_id": "req-7",
  "response": null,
  "error": {
    "code": "invalid_request_error",
    "message": "messages: field required",
    "status_code": 400
  }
}

A request whose 24-hour window elapsed before it ran gets an error line with code: "batch_expired". A record that fails validation (for example one with "stream": true, which is not allowed in a batch) also lands in the error file rather than failing the whole batch.

Reconciling usage

Each successful response.body carries its own usage object, billed exactly like an online request but at the batch rate. For a record that tool-called, that usage is the sum across all tool_rounds + 1 turns, so it is the whole record's cost in one place rather than the last turn's. The batch object also exposes a rolled-up usage (input_tokens, output_tokens, cost_nano_usd) so you can read total spend without summing every line. See Pricing & billing.

On this page