Modulify

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_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.

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.