# Requests and responses

Source: https://modulify.ai/docs/api/requests-and-responses

How to call a method, what to put in the body, and the envelope every answer comes back in.

Every method is called the same way, and every answer has the same shape, so one small helper in your code can call all of them.

## The request

| Part | Value |
| --- | --- |
| Method | `POST` |
| Address | `https://api.modulify.ai/v1/` followed by the method name, such as `/v1/list_sites` |
| `Authorization` header | `Bearer` and your token. See [Authentication](https://modulify.ai/docs/api/authentication) |
| Body | A JSON object of the method's arguments |

The arguments are exactly the ones on the method's page, with the same names and types. A few rules apply to every method:

- **An empty body means no arguments.** `list_workspaces` takes none, so it can be called with no body at all.
- **The body must be a JSON object.** An array, a string or broken JSON is refused with a `400`.
- **No `Content-Type` is needed.** The body is read as JSON whatever the header says, so `curl -d` works as it is. Sending `Content-Type: application/json` is still good practice.
- **Unknown arguments are refused.** A name the method does not take comes back as a `400` naming it, rather than being ignored, so a typo never goes unnoticed.
- **Leave out what you do not need.** Every argument a method does not list as required can be omitted, and each page says what happens when it is.
- **The whole body can be up to 150 MB.** Methods that take a file, such as `upload_storage_file`, take its bytes base64 encoded in a string argument and set their own lower limit, stated on their page.

## Examples

The same call in three languages. It lists the sites of one workspace.

```bash
curl -X POST https://api.modulify.ai/v1/list_sites \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"workspaceId":"WORKSPACE_ID","offset":0}'
```

```javascript
const response = await fetch('https://api.modulify.ai/v1/list_sites', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.MODULIFY_TOKEN}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ workspaceId: 'WORKSPACE_ID', offset: 0 })
})

const result = await response.json()

if (!result.success) throw new Error(`${result.code}: ${result.message}`)

console.log(result.count, result.data)
```

```python
import os
import requests

response = requests.post(
    'https://api.modulify.ai/v1/list_sites',
    headers={'Authorization': f"Bearer {os.environ['MODULIFY_TOKEN']}"},
    json={'workspaceId': 'WORKSPACE_ID', 'offset': 0},
    timeout=60
)

result = response.json()

if not result['success']:
    raise RuntimeError(f"{result['code']}: {result['message']}")

print(result.get('count'), result['data'])
```

## The response

Every answer is a JSON object with the same fields, whether the call worked or not:

```json
{
  "success": true,
  "message": "Projects were listed successfully.",
  "data": [
    { "_id": "6710f0c2a1b2c3d4e5f60718", "Name": "Lisbon Pottery Studio" }
  ],
  "count": 14,
  "code": 200,
  "version": "0.0.1740"
}
```

| Field | What it holds |
| --- | --- |
| `success` | `true` when the method did what it was asked, `false` otherwise |
| `message` | What happened, in plain English. Errors end with `!` |
| `data` | The result. Its shape depends on the method, and each method's page describes it. `null` when there is nothing to return |
| `count` | On methods that list things, the total number of items, not just the ones on this page |
| `code` | The HTTP status code, repeated in the body |
| `version` | The version of the Modulify server that answered |

Check `success`, or the HTTP status, before reading `data`. A refusal can still carry details in `data`, such as the `reason` a site could not be created. See [Errors](https://modulify.ai/docs/api/errors).

Results come back in full. Unlike MCP, where a long result is cut to fit a model, the API returns the whole of `data` however large it is.

New fields can appear in `data` at any time, so ignore the ones you do not use rather than failing on them. See [Versioning](https://modulify.ai/docs/api/versioning).

## Headers on every method call

| Header | What it holds |
| --- | --- |
| `X-Request-Id` | The id of this call in Modulify's records. Quote it when you ask for help |
| `X-RateLimit-Limit` | How many calls the token may make in a minute |
| `X-RateLimit-Remaining` | How many are left in the current minute |
| `X-RateLimit-Reset` | Seconds until the current minute ends |

`Retry-After` is added when a call is refused for going over the per-minute budget, and `Idempotency-Replayed: true` when an answer was replayed for a repeated `Idempotency-Key`. Answers to authenticated requests are sent with `Cache-Control: no-store`, so nothing in between keeps a copy.

## Three requests that are not methods

| Request | What it does | Token |
| --- | --- | --- |
| `GET /v1/token` | Describes the token: name, scopes, workspaces, expiry and this minute's budget | Required, no scope needed |
| `GET /v1/tools` | Lists the methods the token's scopes allow, each with its arguments as a JSON Schema | Required |
| `GET /v1/openapi.json` | Describes every method in OpenAPI 3.1 | Not needed |

Calling a method's address with `GET` instead of `POST` is refused with a `405` and an `Allow: POST` header.

## Next

- [Errors](https://modulify.ai/docs/api/errors) for every status code.
- [Pagination](https://modulify.ai/docs/api/pagination) for methods that return a page at a time.