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

> ## Agent Instructions
> Automate.ax automations are TypeScript programs.
> Use Bun for installation and command examples.
> Use Automate.ax for the product name and automate.ax for the package, CLI, and domain.
> Treat documented public APIs as current; do not invent transitional or deprecated names.

# Call an external API with a custom action

> Wrap an external HTTP API in a typed, durable Automate.ax action.

Use a custom action when Automate.ax does not provide the external operation you need. The action handler can call an HTTP API with `fetch`, while input and output schemas keep the rest of your automation typed.

Add Zod as a direct project dependency for the action schemas:

```bash theme={null}
bun add zod
```

This example exposes a small HTTP endpoint backed by the public PokéAPI:

```ts automations/look-up-pokemon.automation.ts theme={null}
import {
  automation,
  defineAction,
  onHttpRequest,
  respondToHttpRequest,
} from "automate.ax"
import { z } from "zod"

const pokemonSchema = z.object({
  id: z.number(),
  name: z.string(),
})

const getPokemon = defineAction("Get Pokémon")
  .describe("Gets one Pokémon from PokéAPI.")
  .input(
    z.object({
      name: z.string().trim().min(1),
    }),
  )
  .output(pokemonSchema)
  .handler(async ({ input }) => {
    const response = await fetch(
      `https://pokeapi.co/api/v2/pokemon/${encodeURIComponent(input.name.toLowerCase())}`,
    )

    if (!response.ok) {
      throw new Error(`PokéAPI returned HTTP ${response.status}.`)
    }

    return pokemonSchema.parse(await response.json())
  })

export default automation("Look up a Pokémon", () => {
  const request = onHttpRequest({ waitForResponse: true })
  const pokemon = getPokemon({
    name: request.query.transform((query) => query.name ?? "pikachu"),
  })

  respondToHttpRequest({ body: pokemon })
})
```

Define custom actions at module scope and call them inside the automation callback. Calling `getPokemon` registers durable work and returns a signal; it does not call the API during composition. The handler runs later with schema-validated input, and Automate.ax validates its result before making the output available to `respondToHttpRequest`.

## Handle provider failures

Throw when the provider does not return a usable response. Automate.ax retries execution failures before recording a failed action outcome, so use provider-supported idempotency keys for custom actions that create or change data.

Parse untrusted response data with a maintained schema library. Do not cast `response.json()` to the expected type.

## Use authenticated APIs safely

Do not hardcode API keys in source code or pass credentials through action inputs or signals.

If Automate.ax supports the service but not a particular operation, declare that service with the action builder's `.account(...)` method and use the authenticated API helper exported by the integration subpath. The platform then resolves and refreshes the selected account privately for the handler.

If the service itself is not supported and requires credentials, request a new integration rather than embedding its secret in the automation. Credential-free endpoints, like the example above, can be called directly.

## Deploy and call the endpoint

Deploy the project and use the printed trigger URL:

```bash theme={null}
bunx automate.ax deploy
curl "YOUR_TRIGGER_URL?name=eevee"
```

The request waits up to ten seconds and returns the validated JSON output when the custom action finishes in time. The automation can continue if the caller times out.
