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

# Production runs

> Run a published Method from your app: connect the app, run, and answer questions.

Your app runs a published Method with one call. A worker runs it with the same runtime as `method run`. Each run shows on the dashboard under **Production**. Production runs are in the Startup plan ([Pricing](/guides/pricing)). To run them on Method's servers instead of your workers, see [Method Cloud](/guides/method-cloud).

## 1. Connect the app

Run this in the Method's folder:

```sh theme={null}
method connect path/to/app
```

The command:

* makes a service key named after the app folder, and writes `METHOD_API_KEY` to the app's `.env` (it makes the file or adds the line). It does not show the key. It adds `.env` to `.gitignore`.
* publishes the current Method file if the Method has no published version.
* finds the app's language (Node or Python) and prints the install line and the code to add.
* lists the secret names that the Method declares. Set them where the app runs.

On a laptop, the libraries can also use your CLI sign-in (`method login`) when there is no `METHOD_API_KEY`.

## 2. Run the Method

### Python

```sh theme={null}
pip install https://app.withmethod.ai/downloads/withmethod-0.14.0-py3-none-any.whl
```

```python theme={null}
from fastapi import FastAPI
from withmethod import Method

app, method = FastAPI(), Method()  # METHOD_API_KEY from the environment or .env

@app.post("/members")
def new_member(member: dict):
    run = method.run("wf_…", {"name": member["name"], "links": member["links"]}, idempotency_key=f"member-{member['id']}")
    return {"status": run["status"], "profile": run.get("result")}
```

### Node

```sh theme={null}
npm install https://app.withmethod.ai/downloads/withmethod-sdk-latest.tgz
```

```ts theme={null}
import { Method } from "@withmethod/sdk";

const method = new Method(); // METHOD_API_KEY from the environment or .env
const run = await method.run({ method: "wf_…", inputs: { name, links }, idempotencyKey: `member-${id}` });
console.log(run.status, run.result);
```

The first `method.run` starts a worker in the app's process (Python: in a child process). Nothing else needs to run. The worker stops when no `method.run` call is waiting. To run workers in separate processes, start the app with `new Method({ worker: false })` (Python: `Method(worker=False)`) and run:

```sh theme={null}
METHOD_API_KEY=mk_live_… method worker --method wf_… --concurrency 2
```

Use the app's key for its workers. Each worker claims a queued run, renews its 60-second lease, downloads the saved version, prepares its dependencies once per version, reads the declared secrets from its environment, and sends the run record. When a worker stops, its run goes back to the queue. A run has at most 3 attempts. `--concurrency` sets how many runs one worker runs at the same time.

* `idempotency_key`: the same key returns the same run. The same key with other values returns 409.
* `version`: `"published"` (default) or a version ID. A run stays on one version.
* `method.runs.get(RUN_ID)` and `method.runs.cancel(RUN_ID)` read and cancel a run.

## 3. Questions (ask steps)

An `ask` step stops the run until someone answers. The worker saves the run folder, reports the question, and stops: no process waits. After the answer, a worker continues the run and keeps the finished steps. The run waits until it is answered or cancelled. A step's `limits.timeout_ms` sets a deadline: after it, the run fails with "No answer within …".

**The app answers** when it starts the run with an ask handler. The handler gets the run ID, the step, the question, and the form (the JSON Schema of the step's `out` types). Return the answer, or return nothing and answer later.

```python theme={null}
def on_ask(question):
    if question.step.startswith("approve"):
        return {"approved": True, "note": "Sent by the app."}
    return None  # answer later: question.answer({...}) or method.runs.answer(run_id, {...})

run = method.run("wf_…", inputs, on_ask=on_ask)
```

```ts theme={null}
const run = await method.run({ method: "wf_…", inputs, onAsk: async ({ step, question, form }) => ({ approved: true, note: "Sent by the app." }) });
```

A `webhook_url` also makes the app the answerer: the webhook gets a `needs_input` event with the question, and the app answers with `POST /v1/runs/RUN_ID/answer` `{"answer": {…}}`.

**The team answers** when the app has no handler and no webhook. The question goes to the **Inbox** on the dashboard, and an email goes to the person who last published the Method. The email has a link that answers the question one time, with no sign-in.

The server checks each answer against the step's `out` types. On your computer, `method answer RUN_ID` shows the question, and `method answer RUN_ID --answer '{"approved": true, "note": "…"}'` answers it. Local runs (`method run FILE`) take answers with `--human FILE` as before.

## 4. Run content and privacy

* `run_data: account` (default): the server keeps the inputs, the questions, the answers, and the result. The result is in `GET /v1/runs/RUN_ID` and in the webhook.
* `run_data: device`, or the organization setting **Keep run content on devices**: the library encrypts the inputs, the questions, the answers, and the saved run folder with a key that it derives from the app's `METHOD_API_KEY` (HKDF-SHA256, AES-256-GCM). The server keeps only the encrypted data and a hash of the key, so it cannot read them. Only a worker with the same key can run the run. The result stays on the worker computer: the library returns it when its own worker ran the run, and `method worker` prints it as one JSON line. The webhook carries only `{run_id, status}` and the encrypted question.
* For a device run, the dashboard and the email show only "A question is waiting on run RUN\_ID". Answer it in the app's handler, or with `method answer RUN_ID` in the app folder (its `.env` has the key).

## 5. Webhooks

Each webhook has the header `Method-Signature: t=TIMESTAMP,v1=HMAC`, where HMAC is the hex HMAC-SHA256 of `TIMESTAMP.BODY` with the key's webhook secret (`method keys create --name NAME` prints it once). Verify the raw body:

```ts theme={null}
Method.webhooks.verify(rawBody, request.headers["method-signature"], process.env.METHOD_WEBHOOK_SECRET!);
```

```python theme={null}
Method.webhooks.verify(raw_body, request.headers.get("method-signature"), os.environ["METHOD_WEBHOOK_SECRET"])
```

A failed delivery is sent again up to 5 times, with longer waits each time. A webhook needs a service key: runs started with a CLI sign-in cannot have one.

## HTTP API

All routes take `Authorization: Bearer METHOD_API_KEY`.

* `POST /v1/runs` `{method, inputs, version?, idempotency_key?, webhook_url?}` → `{run_id, version_id, status: "queued"}`.
* `GET /v1/runs/RUN_ID` → status, attempts, timing, steps (names, status, timing), `question` while the run waits, and `result` (account runs).
* `POST /v1/runs/RUN_ID/answer` `{answer}` → the run, queued again.
* `POST /v1/runs/RUN_ID/cancel` cancels a queued or waiting run at once and a running run at the worker's next heartbeat.


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