Requests and responses
How to call a method, what to put in the body, and the envelope every answer comes back in.
On this page
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 |
| 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_workspacestakes 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-Typeis needed. The body is read as JSON whatever the header says, socurl -dworks as it is. SendingContent-Type: application/jsonis still good practice. - Unknown arguments are refused. A name the method does not take comes back as a
400naming 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.
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}'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)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:
{
"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.
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.
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 for every status code.
- Pagination for methods that return a page at a time.