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

# Unattended runs

> Run agents with nobody watching: service-account credentials, bounded permissions, scheduled work, and a way to tell a human when it matters.

Interactive runs forgive a lot - a person is there to notice. Unattended runs need the failure modes handled up front: the right identity, bounded permissions, a schedule, and a way out when something needs a human.

## Use a service account, not your key

A user-owned API key acts as you, with everything you can reach. A service-account key acts as its own identity, so you grant it exactly the workspaces the job needs and revoking it changes nothing else.

```bash theme={null}
# 1. Create the identity
curl -sS -X POST "$ARG_API/api/service-accounts" \
  -H "X-API-Key: $ARG_API_KEY" -H "Content-Type: application/json" \
  -d '{"organization_id": "'"$ORG"'", "name": "Nightly reports"}'

# 2. Mint a key for it. The plaintext key is shown once.
curl -sS -X POST "$ARG_API/api/keys" \
  -H "X-API-Key: $ARG_API_KEY" -H "Content-Type: application/json" \
  -d '{"organization_id": "'"$ORG"'", "name": "nightly", "service_account_id": "'"$SA"'"}'
```

Check what a key actually is before you deploy it:

```bash theme={null}
curl -sS "$ARG_API/api/auth/principal" -H "X-API-Key: $ARG_API_KEY"
```

```json theme={null}
{
  "kind": "service",
  "id": "…",
  "service_account_id": "…",
  "organization": { "id": "…", "name": "Acme", "slug": "acme" }
}
```

<Warning>
  Two things behave differently for service-account keys:

  * `POST /api/workspaces` returns `403`. Create the workspace under the
    organization instead, with
    [`POST /api/organizations/{orgId}/workspaces`](/api-reference/workspaces/create-org-workspace).
  * The [automation endpoints](/api-reference/automations/run-automation) require
    a user principal, so a service-account key gets `403` there. Use a user-owned
    key for those, or deploy the automation once and let its trigger run it.
</Warning>

See [API keys](/guides/api-keys) for the permissions that govern creating each kind.

## Bound the run

An unattended run should be able to do its job and nothing more.

| Guard                 | Why it matters when nobody is watching                          |
| --------------------- | --------------------------------------------------------------- |
| `read_only: true`     | An analysis job that never writes cannot corrupt the workspace. |
| `max_tool_iterations` | Caps a turn that would otherwise keep trying.                   |
| Workspace grants      | The service account only sees the workspaces you gave it.       |
| A dedicated workspace | Keeps generated output away from source data.                   |

## Give it durable instructions

Anything you would have said in a follow-up message belongs in the workspace, because there is no follow-up message. Put the conventions in `CLAUDE.md` and the procedure in a skill - see [Skills and subagents](/guides/agents/skills). A scheduled run that reads its own instructions from the workspace is also a run you can fix by editing a file, without redeploying anything.

## Run it on a schedule

Two options, depending on where you want the schedule to live.

**Your scheduler.** A cron job, a CI schedule, or a queue worker calls `POST /api/agent/message` and polls to completion. You own retries and alerting.

**An automation in the workspace.** An `.automation` file carries its own trigger - a schedule, an inbound webhook, or a file landing in a folder - and runs server-side with nothing of yours hosting it. Deploy it once:

```bash theme={null}
curl -sS -X POST "$ARG_API/api/automation/deploy" \
  -H "X-API-Key: $ARG_USER_KEY" -H "Content-Type: application/json" \
  -d '{"workspace_id": "'"$WS"'", "file_path": "/nightly.automation"}'
```

Saving the file is not enough - triggers register on deploy. Pause, resume, or remove them later with the [deployment endpoints](/api-reference/automations/list-deployments). See [Automations](/guides/recipes/automations) for what an automation can do between trigger and finish.

## Tell a human when it matters

A run that fails quietly at 3am is the whole problem. Notify someone in your organization from the job itself:

```bash theme={null}
curl -sS -X POST "$ARG_API/api/notifications" \
  -H "X-API-Key: $ARG_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "title": "Nightly report failed",
    "body": "The revenue rollup errored on 2026-08-12. Check /reports/errors.log.",
    "users": ["ops@example.com"]
  }'
```

Agents can send these themselves during a run, so "if you cannot finish, notify ops" is a legitimate instruction to put in a skill.

## A complete loop

```bash theme={null}
set -euo pipefail

START=$(curl -sS -X POST "$ARG_API/api/agent/message" \
  -H "X-API-Key: $ARG_API_KEY" -H "Content-Type: application/json" \
  -d '{"workspace_id": "'"$WS"'", "message": "Build this month'"'"'s revenue report.",
       "max_tool_iterations": 40}')

CHAT=$(echo "$START" | jq -r .chat_id)
CALL=$(echo "$START" | jq -r .call_id)
JOB=$(echo "$START"  | jq -r .job_id)

# Poll, with a ceiling so a stuck run cannot block the scheduler forever.
for _ in $(seq 1 200); do
  STATUS=$(curl -sS "$ARG_API/api/agent/status/$CALL?chat_id=$CHAT" \
    -H "X-API-Key: $ARG_API_KEY" | jq -r .status)
  [ "$STATUS" = "pending" ] || break
  sleep 5
done

if [ "$STATUS" != "completed" ]; then
  curl -sS -X POST "$ARG_API/api/agent/stop/$JOB?chat_id=$CHAT" \
    -H "X-API-Key: $ARG_API_KEY" || true
  curl -sS -X POST "$ARG_API/api/notifications" \
    -H "X-API-Key: $ARG_API_KEY" -H "Content-Type: application/json" \
    -d '{"title": "Nightly report did not finish", "body": "Status: '"$STATUS"'"}'
fi

curl -sS -X POST "$ARG_API/api/agent/cleanup/$JOB?chat_id=$CHAT" \
  -H "X-API-Key: $ARG_API_KEY"
```

## Related

* [Agent runs](/guides/agents/runs) - the endpoints this loop uses
* [API keys](/guides/api-keys) - creating and revoking credentials
* [Automations](/guides/recipes/automations) - triggers that live in the workspace
