# Write a script (/en/docs/developers/automation/guides/write-script)



A script exports a default function that receives input. Its return value becomes the run's `output`.

Before running this page, complete quickstart steps 1–3 in the same Bash session: [set `PRAXIS_API_URL` and `PRAXIS_API_KEY`](/en/docs/developers/automation/quickstart#environment), [define `SOURCE` and run it](/en/docs/developers/automation/quickstart#run-source), then [define and run `read_result`](/en/docs/developers/automation/quickstart#read-result). The helper checks for answer 42, as expected by the arithmetic examples here.

## Entry point and schemas [#entry-point-and-schemas]

Save this as `script.ts`:

```typescript
export default async (input: { n: number }): Promise<{ answer: number }> => {
  console.log("Adding one", input.n);
  return { answer: input.n + 1 };
};
```

The input schema comes from parameter types. The output schema comes from an explicit return type, with `Promise` unwrapped. Supported forms include primitives, objects, arrays, unions and resolvable local type aliases. Unsupported types produce an empty schema and a warning. An empty schema does not validate your fields. Do not rely on a return expression to infer output constraints when the return annotation is missing.

[Validate the definition](/en/docs/api/native/automation/validateDefinition) to inspect the generated schemas before publishing:

```bash
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/validate" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source script.ts '{kind:"script",source:$source}')" \
  | jq -e '.valid == true and .input_schema.properties.n.type == "number" and .output_schema.properties.answer.type == "number"'
```

Expected: `true` and exit code 0. Inspect `errors` and `warnings` in the full validation response when a type cannot be inferred.

## Execution and logs [#execution-and-logs]

```bash
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source script.ts \
    '{source:$source,language:"ts",input:{n:41},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
read_result "$RUN_ID"
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs/$RUN_ID/logs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"
```

Expected: the result assertion prints `true`; the log response includes `Adding one 41`. `console.log/info/debug` produce `info` entries; `console.warn` and `console.error` retain their levels. Logs are separate from output: use `return` for data the caller needs. [Read logs](/en/docs/api/native/automation/listRunLogs), or subscribe with `Accept: text/event-stream`.

## The global praxis object [#the-global-praxis-object]

The runtime supplies `praxis`. No import, API Key or package installation is required inside a script. Its SDK entry points are listed below. Methods use the reference pages' `path`, `query` and `body` options, for example `praxis.automation.runs.create({body:{target,input}})`. The initiator's permissions still govern access.

| Entry point           | Reference                                        |
| --------------------- | ------------------------------------------------ |
| `praxis.agent`        | [Agent](/en/docs/api/native/agent)               |
| `praxis.model`        | [Model](/en/docs/api/native/model)               |
| `praxis.file`         | [File](/en/docs/api/native/file)                 |
| `praxis.sandbox`      | [Sandbox](/en/docs/api/native/sandbox)           |
| `praxis.search`       | [Search](/en/docs/api/native/search)             |
| `praxis.database`     | [Database](/en/docs/api/native/database)         |
| `praxis.automation`   | [Automation](/en/docs/api/native/automation)     |
| `praxis.organization` | [Organization](/en/docs/api/native/organization) |
| `praxis.meta`         | [Meta](/en/docs/api/native/meta)                 |

Scripts cannot access external networks directly. Use platform capabilities and `await` calls to receive their results and handle failures.

## Call a built-in action [#call-a-built-in-action]

`call("namespace/name", input)` takes an Action reference and an input object and returns the Action's output. It is one step of the current run: it has its own input and output and takes one row in the [call ledger](/docs/api/native/automation/listRunCalls); it does not create a child run. `praxis` is the platform namespace, and a bare name resolves only to platform Actions. A call always uses the Action's current revision; `@version` is not supported yet, and the `namespace::name` form is no longer accepted.

A failed call throws an error carrying `code`, `message`, `hint`, the call-ledger position `seq` and `request_id`. Several calls can run concurrently with `Promise.all`. `praxis.*` is the platform SDK only: `call(...)` and `praxis.ocr(...)` were removed and fail at runtime with a hint to use `call`.

Save this as `chat.ts`. It calls `praxis/chat`. The model is built into the action and maintained by its author; callers do not select a model. The same applies to `praxis/ocr`, `praxis/image` and `praxis/embed`. The legacy `model` field is deprecated and ignored. This call incurs model usage charges:

```typescript
export default async (input: { prompt: string }) => {
  return await call("praxis/chat", {
    messages: [{ role: "user", content: input.prompt }],
    stream: false,
  });
};
```

```bash
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source chat.ts \
    '{source:$source,language:"ts",input:{prompt:"Say hello in one sentence."},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs/$RUN_ID" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" | jq '{id,status,output,error}'
```

Expected: terminal status `completed`, model output in `output`, and null `error`. If still pending or running, repeat the final GET until terminal. For `faulted`, inspect the error; HTTP success alone does not prove a successful model call. [Retrieve the action](/en/docs/api/native/automation/getAction) at `praxis/chat` for its complete input and output contract.

## Call your own action [#call-your-own-action]

This example [creates an action](/en/docs/api/native/automation/createAction), then calls its full name. The generated script contains a literal reference so it can be checked before publication.

```bash
ACTION_NAME="add-one-$(date +%s)-$RANDOM"
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/actions" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg name "$ACTION_NAME" '{
    namespace:"docs-samples",name:$name,
    input_schema:{type:"object",properties:{n:{type:"number"}},required:["n"],additionalProperties:false},
    output_schema:{type:"object",properties:{answer:{type:"number"}},required:["answer"],additionalProperties:false},
    implementation:{kind:"code",language:"ts",source:"export default async (input: { n: number }) => ({ answer: input.n + 1 });"}
  }')" | jq -e '.id | startswith("act_")'
SOURCE="export default async (input: { n: number }) => await call(\"docs-samples/$ACTION_NAME\", input);"
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg source "$SOURCE" \
    '{source:$source,language:"ts",input:{n:41},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
read_result "$RUN_ID"
```

Expected: both creation and result checks print `true`, with answer 42. [Archive the action](/en/docs/api/native/automation/archiveAction) when finished. Actions are reusable capabilities, but cannot orchestrate other actions, scripts or workflows from inside their implementation.

## Call another script or workflow [#call-another-script-or-workflow]

`invoke("namespace/name@version", input)` starts another published script or workflow as a **child run**, waits for it to finish and returns the child's output; leave out `@version` for the latest. Unlike `call`, which is one step inside the current run and creates no child run, `invoke` creates a child run with its own ID, status and logs, and the [call record](/docs/api/native/automation/listRunCalls) points to it through `child_run_id`.

```typescript
export default async (input: { urls: string[] }) => {
  // Use Promise.all to run several invokes at once; child runs start through a queue.
  const pages = await Promise.all(input.urls.map(url => invoke("team/fetch-page", { url })));
  return { pages };
};
```

* **The wait is capped**: `invoke` waits synchronously, bounded by the parent run's `deadline`. At the cap it throws `INVOKE_TIMEOUT` with the child run ID in the message; the child is not cancelled, so you can read its result separately. For long waits use a workflow `run:` step, which consumes no compute while it waits.
* **Failures**: a child that fails, is cancelled or cannot be created makes `invoke` throw; `code` is the child's error code and `message` carries the child run ID. A missing or archived target fails immediately and nothing is queued.
* **Cancel and timeout**: when the parent run is cancelled or fails, children it is still waiting on are cancelled with it.
* **Finding child runs**: [list runs](/docs/api/native/automation/listRuns) filtered by `parent_run_id` for each child's ID, target, status and duration.
* **Limits**: at most 100 child runs per run, 20 executing at once (the rest queue), nesting up to 10 levels, and a run cannot invoke itself.

A script cannot start a detached child with `invoke`; to start a run without waiting, call [create run](/docs/api/native/automation/createRun).

## Output and time budgets [#output-and-time-budgets]

Return JSON-serializable data. Do not return `undefined`, BigInt or circular references. UTF-8 encoded JSON output must fit within 1 MiB; larger output faults the run. Store large results in a file or other storage and return a reference.

The default script wall-clock limit is five minutes. CPU time is capped at 30 seconds and further bounded by the remaining deadline. Waiting for a platform call differs from executing pure computation; increasing wait time does not fix an infinite loop. See [limits and errors](/en/docs/developers/automation/limits-and-errors) for the full limits.

## Diagnosing errors [#diagnosing-errors]

| Code                 | Original message                  | Action                                                                                                |
| -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `SCRIPT_ERROR`       | `脚本执行出错`                          | Inspect the original exception and user-code locations in detail, fix the script and start a new run. |
| `CPU_LIMIT_EXCEEDED` | `脚本 CPU 时间超出上限`                   | Remove infinite loops and reduce computation per batch.                                               |
| `PAYLOAD_TOO_LARGE`  | `Payload exceeds 1 MiB`           | Reduce JSON output and return a storage reference.                                                    |
| `INPUT_INVALID`      | `Value must be JSON serializable` | Return an explicit JSON value such as an object or null.                                              |

Execution failures appear in `Run.error`. Rejected create requests return an HTTP problem response. Handle these separately.

[API reference →](/en/docs/api/native/automation) · [Cookbook →](/en/docs/cookbook)
