# Web service automations

> How a web service automation turns an APIANT automation into a synchronous HTTP endpoint that returns a status and body to the caller, including CORS, error responses and the absence of automatic retries.

A web service automation is an HTTP endpoint backed by an automation. The caller sends a request to the automation's URL, the automation runs while the caller waits, and the caller receives the HTTP status and body the automation sets. A webhook trigger, by contrast, accepts the request and runs the automation in the background.

## Build a web service

Describe the endpoint, including its inputs and what it returns, for example: "Build a web service that takes an `email` query parameter, looks the contact up in HubSpot, and returns their company name as JSON, or a 404 if there is no match."

A web service automation uses the two operations of the **Web Service** app:

1. **HTTP request received (instant)**, the trigger.
2. The steps that do the work.
3. **Return HTTP response**, which sets what the caller receives.

When the automation is committed, Claude or the assistant returns the web service URL. It has this shape:

```text
https://hooks.apiant.ai/webservice-YOUR_WEB_SERVICE_ID
```

`YOUR_WEB_SERVICE_ID` stands for the identifier Claude or the assistant gives you; use the full URL exactly as returned. Each web service automation has its own URL. Turn the automation on before calling it.

## Request data

The URL accepts `GET`, `POST`, `PUT` and `HEAD`. The trigger exposes the request to later steps:

| Field | Contents |
|---|---|
| `trigger.params.<name>` | One query parameter |
| `trigger.body.<field>` | One field of a JSON body, parsed automatically |
| `trigger.body` | The raw body, when it is not JSON |
| `trigger.path`, `trigger.query_string`, `trigger.content_type` | The request path, the raw query string and the content type |

The trigger's **Query parameters** and **POST payload example** settings document the endpoint's contract for people reading the automation. The platform does not use them to parse the request: every query parameter and JSON field is available whether or not it is listed.

## The response

**Return HTTP response** has three inputs:

| Input | Default | Notes |
|---|---|---|
| **HTTP status code** | `200` | A whole number from 100 to 599. Other values are refused when the automation is saved, or when the step runs if the value comes from a mapping. |
| **Response data** | Empty | The body, as text. To return JSON, build the JSON text in the mapping or in a script step. |
| **Content type** | `text/plain; charset=utf-8` | Set `application/json` when returning JSON |

Put **Return HTTP response** at the end of every branch that can finish the run. If more than one runs, the last one sets the response. If none runs, the caller receives an empty `200`.

Keep the automation short, such as one lookup or one API call and the response. For long work, return `202` with an ID and continue in a separate webhook automation.

## Errors the caller can receive

| Status | Body | Cause |
|---|---|---|
| `404` | `not found` or `web service not found` | The URL is wrong or the automation was deleted |
| `429` | `monthly usage limit reached` | The account reached its monthly web service call or task limit |
| `500` | The step's error message | A step failed. The run stops and the error is returned. |
| `500` | `web service did not return a synchronous response` | The automation paused (for example on a snooze, form or approval step) or was halted instead of finishing |
| `503` | `web service is not active` | The automation is off |

Error responses are plain text. To return a clean error of your own, such as a `404` with a message, ask Claude to add a condition that ends in **Return HTTP response** with that status.

## No automatic retries

A failed web service run is not retried and is not queued for later. The caller is waiting, so the calling system decides whether to send the request again.

## Calling from a browser

Web service responses, including error responses, carry `Access-Control-Allow-Origin: *`, and the URL answers `OPTIONS` preflight requests. JavaScript on your own site can send a JSON `POST` and read the reply with no configuration. The endpoint does not allow credentialed requests (cookies); the URL is the only credential. Because anyone who sees the page can see the URL, never return data in a web service response that a visitor to that page must not see.

```javascript
const res = await fetch("https://hooks.apiant.ai/webservice-YOUR_WEB_SERVICE_ID", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jordan@example.com" }),
});
const data = await res.json();
```

## Testing

A test run from Claude or the assistant runs the automation's steps but does not go through the URL. To test the real endpoint, turn the automation on and send a request from your own tools. Claude or the assistant can give you a ready-made request built from the trigger settings.

```bash
$ curl -X POST "https://hooks.apiant.ai/webservice-YOUR_WEB_SERVICE_ID" \
    -H "Content-Type: application/json" \
    -d '{"email":"jordan@example.com"}'
```

## Usage

Each call counts against your plan's monthly web service call limit and also consumes tasks. See [Plans, usage and billing](/docs/account/plans-and-limits).

## Troubleshooting

| Symptom | Cause and fix |
|---|---|
| `503 web service is not active` | Turn the automation on. |
| A step does not receive a query parameter or JSON field value | The request did not include that parameter, or the mapping uses the wrong prefix. Query parameters are `trigger.params.<name>`; JSON fields are `trigger.body.<field>`. |
| A strict client rejects the JSON response | **Content type** is empty, so the response is `text/plain`. Set it to `application/json`. |
| The caller times out | The automation does too much work in the request. Shorten it or return `202` and continue elsewhere. |
| The response comes from the wrong branch | More than one **Return HTTP response** ran. Keep one per branch, at its end. |

## Next steps

- [Webhook triggers](/docs/automations/triggers/webhooks)
- [Conditions, loops and parallel branches](/docs/automations/control-flow)
- [Public URLs](/docs/reference/urls)
- [Limits](/docs/reference/limits)
