> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lusor.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Send a turn

> Send a task to an agent, retry safely, and start sessions without an agent.

A **turn** is one task you send to an agent. Each turn creates a **session**, the record of the agent's work on the phone.

## Send it

```bash cURL theme={null}
curl https://api.lusor.ai/v1/agents/<agent id>/turns \
  -H "X-Api-Key: $LUSOR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1234" \
  -d '{ "prompt": "Check the delivery status of my last order in the Shop app." }'
```

```python Python theme={null}
import os, requests

r = requests.post(
    "https://api.lusor.ai/v1/agents/<agent id>/turns",
    headers={"X-Api-Key": os.environ["LUSOR_API_KEY"], "Idempotency-Key": "order-1234"},
    json={"prompt": "Check the delivery status of my last order in the Shop app."},
)
turn = r.json()["data"]
print(turn["sessionId"], turn["queued"], turn["needsDevice"])
```

```javascript Node.js theme={null}
const res = await fetch("https://api.lusor.ai/v1/agents/<agent id>/turns", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.LUSOR_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "order-1234",
  },
  body: JSON.stringify({ prompt: "Check the delivery status of my last order in the Shop app." }),
});
const { data: turn } = await res.json();
console.log(turn.sessionId, turn.queued, turn.needsDevice);
```

`prompt` is required, up to 8,192 characters. The call returns `202` straight away:

```json Response (202) theme={null}
{
  "data": {
    "id": "4f1d…",
    "sessionId": "mobile_use_9c2e…",
    "status": "queued",
    "queued": false,
    "needsDevice": false,
    "deviceId": "8e2c…",
    "recording": true
  }
}
```

`sessionId` identifies the session. [Follow it](https://docs.lusor.ai/agents/follow-a-session.md) to see the agent's progress and result.

## What happens next

| Response | Meaning |
| - | - |
| `queued: false` | The agent has started on its phone. |
| `queued: true` | The phone is busy with another session. The turn waits and starts when the phone is free. The response includes an `invocationId`. |
| `needsDevice: true` | The agent has no phone, so the turn is saved but never starts. [Give the agent a phone](https://docs.lusor.ai/agents/create-an-agent.md#give-it-a-phone-or-move-it) and send the task again. |

To withdraw a waiting turn before it starts, see [Stop a session](https://docs.lusor.ai/agents/stop-a-session.md).

Every turn is recorded on video. See [Recordings and replay](https://docs.lusor.ai/agents/recordings-and-replay.md).

## Safe retries

Send an `Idempotency-Key` header with a value unique to the task, up to 256 characters. If the request times out and you send it again with the same key, Lusor returns the original turn instead of starting a second one. Without the header, every request creates a new turn.

You can also send the key as `idempotencyKey` in the body. The header wins when you send both.

## Errors

| Status | `error` | What to do |
| - | - | - |
| `400` | `bad_request` | `prompt` is missing or empty, or a field is invalid. Read `message`. |
| `402` | `credits_exhausted` | The workspace is out of credits. See [Credits and minutes](https://docs.lusor.ai/billing/credits-and-minutes.md). |
| `404` | `not_found` | The agent does not exist, or belongs to another workspace. |
| `409` | `device_releasing` | The agent's phone is being removed from your Phone plan. Move the agent to another phone. |
| `409` | other codes | No phone is available for this turn right now. Read `message`, then try again. |

## Sessions without an agent

To give one task to a phone without creating an agent, start a session on the phone directly. You need your workspace id, which is the `workspaceId` of any agent.

```bash theme={null}
curl https://api.lusor.ai/v1/workspaces/<workspace id>/devices/<device id>/sessions \
  -H "X-Api-Key: $LUSOR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: price-check-1" \
  -d '{ "prompt": "Find the price of a 12-pack of sparkling water in the grocery app." }'
```

The call returns `202` with the session's `id` and `status: "queued"`. Follow it with the same [session endpoints](https://docs.lusor.ai/agents/follow-a-session.md).

A session without an agent takes more options than a turn:

| Field | What it does |
| - | - |
| `schema` | A JSON Schema the output must follow. See [Structured output](https://docs.lusor.ai/agents/structured-output.md). |
| `model` | The model for this session. See [Choose a model](https://docs.lusor.ai/agents/choose-a-model.md). |
| `recording` | `true` records a video of the session. Off by default. See [Recordings and replay](https://docs.lusor.ai/agents/recordings-and-replay.md). |
| `maxDurationSecs` | A cap on how long the session may take, in seconds. Lusor also applies its own upper limit. |
| `app` | The app the task is about, if you want to record it on the session. |

A phone works on one session at a time. If it is busy, this call does not wait: it returns `429` with `error: "device_busy"` and the `currentSessionId` of the session holding the phone. Other refusals:

| Status | `error` | Meaning |
| - | - | - |
| `400` | `validation_error` | A field is invalid. `details` lists each problem. |
| `402` | `credits_exhausted`, `billing_required` | Out of credits, or the Lusor Phone is not covered by an active paid plan. |
| `409` | `internet_route_inactive` | The Lusor Phone's internet connection is not active yet. Try again once it is. |
| `409` | `device_releasing` | The phone is being removed from your Phone plan. Use another phone. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.