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

> ## Agent Instructions
> AutoGrowth runs social accounts on real phones. Everything a page describes happens on a physical device in the customer's own fleet, never through a social platform's API.
> The Companion is a macOS desktop app that runs on the customer's own Mac and manages the phones over USB — iPhones through WebDriverAgent, GrapheneOS phones through ADB and scrcpy. Nothing runs while the Companion is closed.
> The Companion builds, signs and installs WebDriverAgent on each iPhone itself. Never tell a customer to sign WebDriverAgent by hand in Xcode; Xcode is a prerequisite the Companion drives, not a step the customer performs.
> Autogrowth MAX is a one-time, invite-only lifetime license bought from the organization's credit balance, not a subscription tier. It is required to run any automation, workflow or Incubator program. Without a price set for that organization, the purchase is refused.
> An automation is one of AutoGrowth's built-in definitions; a script is a graph the customer builds in the Script Builder. The Script Builder is locked platform-wide, so never present building a script as something a customer can do today.
> An API key is a bare 64-character hex string with no prefix. It is shown once, at creation, is sent as `Authorization: Bearer <key>`, and can never be read back.
> An API key sees only the key owner's own accounts, content, collections, prompts, scripts and executions. Devices and tags are organization-scoped, and a non-owner sees only the devices assigned to them or carrying one of their tags. The automation catalog is global.
> There is no pagination. List endpoints return the whole set; the only limiting parameter anywhere is `limit` on `GET /v1/executions`, which defaults to 50 and has no offset to page with.
> Remote control and the live screen are dashboard-only, over WebSocket and MJPEG. Neither is part of the public API.
> The MCP server is hosted at https://mcp.autogrowth.farm and authenticates with OAuth. It is not an npm package and there is nothing to install locally. Its tools inherit exactly the scope of the API key behind them.
> Android support is partial and GrapheneOS-only: those phones appear in the fleet and can be watched and remote-controlled, but they cannot run scripts or automations.
> Never document Hi Katie or any persona feature. When a page and the code disagree, the code wins — check a claim against the source before repeating it.

# Errors

> The error response shape, the status codes the API returns, and which ones are worth retrying.

Almost every failure carries the same body:

```json theme={"dark"}
{ "error": "A human-readable message" }
```

Some failures add a machine-readable `code`. Two places use a different shape
entirely, and both are documented below.

<Info>
  `error` is a message for a human, not a stable identifier. Log it, show it to an
  operator, branch on the HTTP status or on `code` — but do not build control flow
  on the exact string.
</Info>

## Status codes

| Status | When                                                                                                                                                                                                       |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The request failed schema validation, or names something unusable — an account not linked to that phone, an account whose platform does not match the automation, a run that is not in a cancellable state |
| `401`  | Missing, malformed, unknown or deleted API key                                                                                                                                                             |
| `402`  | The phone's slot has expired                                                                                                                                                                               |
| `403`  | Valid key, but the caller is not allowed: insufficient permissions, a disabled user, no organization, or a missing entitlement                                                                             |
| `404`  | No such row **for this key**, or no such automation                                                                                                                                                        |
| `409`  | The row already exists. Only persona binding creation returns it                                                                                                                                           |
| `413`  | The upload would exceed your organization's storage allowance                                                                                                                                              |
| `500`  | An unhandled failure. The body is always `{"error": "Internal server error"}`                                                                                                                              |

A request to a path the API does not serve answers `404` with a **plain-text** body
rather than the JSON envelope. If you get text back, the URL is wrong.

## Coded errors

Four failures carry a `code` alongside the message. These are the ones worth
branching on, because each has a specific fix and none of them will resolve on a
retry.

| Code                            | Status | Meaning                                                                  | Fix                                                                                                                                            |
| ------------------------------- | ------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `MAX_REQUIRED`                  | `403`  | The run is an automation and the organization has no Autogrowth MAX      | Buy the licence from the dashboard. It is invite-only, so ask if it is not offered. See [Billing](/guides/billing)                             |
| `CREATE_ACCOUNT_ADDON_REQUIRED` | `403`  | The create-account automation was scheduled without its paid add-on      | Buy the add-on. There is no admin bypass; access is purely payment-based                                                                       |
| `SLOT_EXPIRED`                  | `402`  | The target phone's slot is not active, so nothing can be scheduled on it | Renew the slot for that phone, then schedule again                                                                                             |
| `STORAGE_LIMIT_EXCEEDED`        | `413`  | The upload would push the organization over its media allowance          | Delete media, or buy extra storage. [`GET /content/storage`](/api-reference/content/get-storage-info) reports what is used and what is allowed |

A typical body:

```json theme={"dark"}
{
	"error": "Autogrowth MAX is required to run automations.",
	"code": "MAX_REQUIRED"
}
```

In [`POST /executions/bulk`](/api-reference/executions/bulk-create-executions) these
codes appear **per item** instead of on the response. The call answers `201`, and
each rejected item shows up in `failed` with its `deviceId`, an `error`, and a
`code` when there is one:

```json theme={"dark"}
{
	"created": [],
	"failed": [
		{
			"deviceId": "0198f8a0-1111-7aaa-8ccc-3e5d7c9b1a24",
			"error": "Device slot has expired. Renew your slot to schedule executions.",
			"code": "SLOT_EXPIRED"
		}
	]
}
```

## Validation failures

A body, query string or path parameter that fails schema validation does **not**
use the `{ error }` envelope. The validator returns its raw result, so you get a
`400` shaped like this:

```json theme={"dark"}
{
	"success": false,
	"error": {
		"name": "ZodError",
		"issues": [
			{
				"code": "invalid_type",
				"expected": "string",
				"path": ["scheduledAt"],
				"message": "Invalid input: expected string, received undefined"
			}
		]
	}
}
```

The `issues` array is Zod's, verbatim, and its exact contents depend on how the
value was wrong. Read `path` to find the offending field. A client that parses
error bodies has to handle both this shape and `{ error }`.

<Warning>
  Not every `400` is a validation failure. Creating an execution can also answer
  `400` with a plain `{ error }` — an account that is not linked to the device, an
  account on the wrong platform for the automation, or a container that has moved
  or been deleted. Check for `error` before assuming `issues` exists.
</Warning>

## The upload endpoint's variant

[`POST /content/upload`](/api-reference/content/upload-content) is multipart, so
its form fields are validated by hand. A field failure answers `400` with a third
shape:

```json theme={"dark"}
{
	"error": "Invalid form data",
	"details": {
		"formErrors": [],
		"fieldErrors": {
			"title": ["Too big: expected string to have <=200 characters"]
		}
	}
}
```

`details` is a flattened Zod error: `fieldErrors` keyed by field name, plus
`formErrors` for anything that did not belong to one field. A request with no file
at all answers `400` with `{ error: "No file provided" }` and no `details`.

## 401 and 403 are different problems

`401` is a **credential** problem: the header is missing or malformed, or the key
matches nothing. Retrying with the same key is pointless, and so is refreshing
anything — there is nothing to refresh.

`403` is an **authorization** problem: the key is good, and the user behind it is
not allowed to do this. That covers a role without the required permission, a
disabled user, a user in no organization, and the two entitlement codes above.
Never sign a user out or re-authenticate on a `403`; surface it to a human.

Full bodies for both are in
[Authentication](/api/authentication#auth-error-responses).

## Not found, or not yours

Most resources are scoped to the key owner. A row that exists but belongs to a
teammate answers exactly like a row that does not exist — `404`, same message. You
cannot tell the two apart, and that is deliberate.

The same goes for admin-only automations: `GET /automations/{id}` answers `404`
rather than `403` for a key that may not see them.

## What to retry

| Situation                         | Retry?                                            |
| --------------------------------- | ------------------------------------------------- |
| `500`                             | Yes, with backoff. Something failed on our side   |
| `401`, `403`, `404`, `409`, `413` | No. Nothing about the request will work next time |
| `400`                             | No. Fix the request                               |
| `402` `SLOT_EXPIRED`              | Only after the slot is renewed                    |

Scheduling is not idempotent. There is no idempotency key, so a create that timed
out may or may not have landed — read
[`GET /executions`](/api-reference/executions/list-executions) filtered by device
and time range before sending it again.
