# Long-running work

Source: https://modulify.ai/docs/api/long-running-work

Methods that answer before the work is done, and how to follow each one to the end.

Building a site, publishing it, taking a backup and restoring files take longer than one request should wait. The methods that start them answer as soon as the work is under way, and a second method reports how it is going. Your code calls the second one every few seconds until the work reaches an end.

| Work | Started by | Followed with | Finished when |
| --- | --- | --- | --- |
| A generation by the site agent | `send_message`, `create_site` | `get_job` | `state` is `succeeded`, `failed` or `cancelled` |
| A publish | `publish_site`, `trigger_deploy_hook` | `get_publish_status` | `running` is false |
| A storage archive | `create_storage_backup` | `list_storage_backups` | The row reads `ready` or `failed` |
| A database archive | `create_database_backup` | `list_database_backups` | The row reads `ready` or `failed` |
| A storage restore | `restore_storage_backup` | `get_storage_restore_status` | `active` is false and `Status` is `completed` or `failed` |

Wait a few seconds between calls. Each one spends from the token's per-minute budget, and asking more often does not make the work go faster. See [Rate limits](https://modulify.ai/docs/api/rate-limits).

## Generations

`send_message` hands a prompt to the site agent. When the site is free it starts at once and answers with `queued: false` and a `jobId`. When the agent is busy, the message joins the site's queue and the answer carries `queued: true` instead. `create_site` creates a site and starts its first build in one call, and answers with the new site and a `jobId`.

Follow a generation with `get_job`:

```bash
curl -X POST https://api.modulify.ai/v1/get_job \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"projectId":"SITE_ID","jobId":"JOB_ID","cursor":0}'
```

The answer carries the `state` and the events the run has produced, such as text, file changes and tool calls. Send the `cursor` from each answer back on the next call and you receive only the events that are new. `count` is the number of events in this answer, and `limit` asks for up to 500 at a time.

| `state` | What it means | What to do |
| --- | --- | --- |
| `running` | The agent is working | Call again in a few seconds |
| `stalled` | No heartbeat for over a minute | Keep calling. It is not over: Modulify resumes or closes a run that has produced nothing for 15 minutes |
| `succeeded` | The run ended normally | Read the result. Check `incomplete` too |
| `failed` | The run ended with an error | `error` and `errorCode` say why |
| `cancelled` | Somebody stopped it | Nothing more will happen |

Four details matter:

- **`incomplete: true` on a succeeded run** means the agent stopped part way through the turn. `incompleteReason` says how. The run was billed and what it changed was kept, so send a follow-up asking it to continue rather than starting over.
- **Error code `SC-6001` on a failed run** means the workspace ran out of AI credits part way through. What it changed was kept and only what the balance covered was charged.
- **`reset: true`** means the run was retried from the start. Throw away the events you collected and carry on from the new `cursor`.
- **Leaving out `jobId`** reads the most recent generation of the site, which is how you find a run your code did not start.

`cancel_job` stops a running generation and keeps whatever the agent had already written. See [Building with prompts](https://modulify.ai/docs/api/building-with-prompts) for the whole conversation, including questions the agent asks and plans waiting for approval.

## Publishes

`publish_site` starts a publish and answers once it has started. If a publish is already running, the answer carries `pending: true` and the `deploymentId` of that run instead of starting a second one, so treat it as success and follow that one.

Call `get_publish_status` until `running` is false, then read `status`:

| `status` | What it means |
| --- | --- |
| `ready` | The new version is live |
| `failed` | The publish failed. `error` has the reason, and `get_publish_logs` has the build output |
| `cancelled` | The publish was stopped |
| `null` | The site has never been published |

Any other value, such as `provisioning` or `deploying`, is a step still in progress. `get_publish_timeline` shows each step with how long it took, and `stop_publish` abandons a publish that has not yet switched over. A publish started by `trigger_deploy_hook` is followed the same way.

## Backups

`create_storage_backup` and `create_database_backup` answer as soon as the archive is reserved, with a row that reads `pending`. The archive itself is built later. Read `list_storage_backups` or `list_database_backups` until the row reads `ready`, or `failed` with the reason. Do not delete or move the files or rows it should hold until it is `ready`: anything removed before the archive reads it is left out.

`create_backup` and `restore_backup` work on the site's pages and components and answer when they are done.

## Storage restores

`restore_storage_backup` is a two-step method, so nothing is overwritten by mistake. The first call, without `confirm`, changes nothing. It answers with a plan: how many files come back, how many are overwritten or deleted, and a `confirm` value. The second call sends the same arguments plus that `confirm` value and starts the restore.

The restore then runs in the background. Call `get_storage_restore_status` until `active` is false. `Status` reads `pending`, `running`, `completed` or `failed`, and counts such as `FilesWritten` and `FilesFailed` show progress. Only `completed` means the files are back. While a restore runs, the methods that change storage are refused and the ones that read it keep working.

## Work that finishes in one call

Exports answer with everything at once. `export_collection`, `export_site_analytics`, `export_site_crons`, `export_site_webhooks` and `export_deploy_hooks` return their data in `data`, with nothing to follow.

## Next

- [Building with prompts](https://modulify.ai/docs/api/building-with-prompts) for driving the site agent.
- [Pagination](https://modulify.ai/docs/api/pagination) for reading long lists.