# Results and handles

A call that cannot answer within its request answers 202 with a results handle instead of waiting. GET /v1/results/{handle} then reads what the call came to. Reading is free and can be repeated; only the key that started the call can read it; a result is kept for 24 hours after its run ended. Over MCP the same handle goes into the tool's handle argument. Nothing is charged before a run ends, and a failed call is not charged.

- Page: https://socialtoai.com/docs/api/results/
- Last updated: 2026-10-17

- GET: `https://api.socialtoai.com/v1/results/{handle}`

## When a call answers with a handle

- **A slow interface.** A few interfaces always take long. They answer with a handle at once; their own page says so.
- **The wait ran out.** Any other call waits up to 40 seconds for the platform. When the result is not there by then, the request answers with a handle and the call goes on.
- **A tool run that goes on in the background.** Its quote says so in `quote.mode`.

The answer is HTTP 202 with a `Retry-After` header and this body. `status` is `running`, and `billing.cost_credits` is 0: nothing is charged until the run has ended.

**202 Accepted** (202, 0 credits)

```
{
  "request_id": "req_00000000-0000-4000-8000-000000000003",
  "schema_version": "v1",
  "status": "running",
  "handle": "res_00000000000040008000000000000003",
  "retry_after_seconds": 5,
  "billing": {
    "cost_credits": 0,
    "balance_credits": 100,
    "today_spend_credits": 0,
    "daily_limit_credits": null,
    "daily_remaining_credits": null,
    "display": "This call: 0 credits. Balance: 100 credits."
  }
}
```

| Field | Type | What it is |
| --- | --- | --- |
| `request_id` | string | Identifies this answer. Quote it when you report a problem. |
| `schema_version` | "v1" | Always "v1". |
| `status` | "running" | Always "running" in this answer. |
| `handle` | string | What the result is read with. |
| `retry_after_seconds` | integer | How soon the handle is worth reading. The same number is in the Retry-After header. |
| `cells` | array | Of a request for several platforms or targets: the answers that have already ended, each as the final answer will hold it. (may be absent) |
| `steps` | array | Of a tool run: its steps as far as they have gone. (may be absent) |
| `billing.cost_credits` | integer | Always 0 here: nothing is charged before the run ends. |
| `billing.balance_credits` | integer | Your balance now. |
| `billing.today_spend_credits` | integer | Credits spent today, in UTC. What the run holds meanwhile is counted here. |
| `billing.daily_limit_credits` | integer \| null | Your daily limit; null when none is set. |
| `billing.daily_remaining_credits` | integer \| null | What is left of the daily limit; null when none is set. |
| `billing.display` | string | One sentence an AI can repeat to you. |
| `waves.planned` | integer |  |
| `waves.done` | integer |  |
| `charge.quote_credits` | integer |  |
| `charge.charged_credits` | integer |  |

## Read the result

**cURL**

```
curl https://api.socialtoai.com/v1/results/res_00000000000040008000000000000003 \
  -H "Authorization: Bearer $SOCIALTOAI_KEY"
```

- **Still running:** HTTP 202 and the same body as above. Wait `retry_after_seconds` and read again.
- **Finished:** exactly what the call would have answered, under its own status: the [envelope](https://socialtoai.com/docs/api/response.md) under 200, or the [error structure](https://socialtoai.com/docs/api/errors.md) under the status of its code. `billing.cost_credits` is what the run was charged; the balance is as of this read.
- **Reading is free.** Read as often as you like; the run is charged once, when it ends, by the same rules as any call.
- **The run broke on our side:** HTTP 500 `internal_error`. Nothing was charged.

## Who can read it

Only the key that started the call. To any other key, another key of the same account included, the handle does not exist: HTTP 404 `not_found`. The handle alone opens nothing: reading it always takes the key. Calls you start in the Playground are read in the console, under your sign-in.

## How long it is kept

A result is kept for 24 hours after its run ended. After that the handle answers HTTP 410 `handle_expired`. To get the data again, send the call again: that is a new call, charged as one.

## As a file

Add `?format=csv` or `?format=ndjson` to get a finished result's rows as a file: a table, or one JSON object a line. While the run is still going the answer is the 202 body above, whatever the format. Another value of `format` is refused with `invalid_params`.

- The columns of the CSV are every field any row has. A nested field is written `a.b`; a list stays in one cell.
- A cell that starts with `=`, `+`, `-` or `@` gets an apostrophe in front, so that a spreadsheet does not run it as a formula.

**cURL**

```
curl "https://api.socialtoai.com/v1/results/res_00000000000040008000000000000003?format=csv" \
  -H "Authorization: Bearer $SOCIALTOAI_KEY" -o result.csv
```

## Over MCP

The 13 tools that charge all take one more argument, `handle`. A call with `handle` only reads that result: nothing else runs, nothing is charged, and the other arguments are not looked at. So when a tool answers `status: "running"`, your AI calls the same tool again with the handle.

### Large answers: more.handle

An answer over MCP holds at most about 60 KB of JSON (roughly 20,000 tokens), so that it does not fill your model's context. A larger result comes as its first rows and a `more` object:

| Field | Type | What it is |
| --- | --- | --- |
| `more.handle` | string | The handle with the place to read on from, written `handle.position`. Call the same tool with it for the next rows. |
| `more.rows_left` | integer | How many rows are still to come. |
| `more.note` | string | One sentence on how to go on, for the AI. |

Reading on is free. Go on until an answer carries no `more`. Over HTTP the same handle, without the position, reads the whole result at once.

## Sending the same request again

A handle does not change how `Idempotency-Key` works. The same key with the same parameters while the run is still going is refused with HTTP 409; once the run has ended it returns the first result again and costs 0.
