Make a call and read the receipt

Every operation is called the same way: POST /invocations with what to call, an id you choose and the operation's input. Every call answers with a receipt that says what happened.

The request

POST https://api.growlocal.site/api/v1/profiles/{businessId}/invocations
Authorization: Bearer gl_…
Content-Type: application/json

The body is a JSON object with three parts:

Part What it is
target What to call. Copy it exactly as the operation's catalogue entry gives it (GET /catalog).
operationId An id you choose for this request: up to 80 characters, new for each new request. Sending the same one again is how you retry (Retries without doing it twice).
input The operation's input, as its API reference page or GET /catalog/{entryRef} describes it.
# A new operationId for each new request; the same one to retry it.
curl https://api.growlocal.site/api/v1/profiles/YOUR_BUSINESS_ID/invocations \
  -H "Authorization: Bearer gl_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "target": {
    "featureId": "YOUR_FEATURE_ID",
    "export": "tables",
    "contractRef": "data.tables.query@1"
  },
  "operationId": "list-tables-2026-11-05-1",
  "input": {}
}'
// A new operationId for each new request; the same one to retry it.
const response = await fetch(
  "https://api.growlocal.site/api/v1/profiles/YOUR_BUSINESS_ID/invocations",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer gl_YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      target: {
        featureId: "YOUR_FEATURE_ID",
        export: "tables",
        contractRef: "data.tables.query@1",
      },
      operationId: "list-tables-2026-11-05-1",
      input: {},
    }),
  },
);
const receipt = await response.json();
console.log(response.status, receipt.status);
import requests

# A new operationId for each new request; the same one to retry it.
response = requests.post(
    "https://api.growlocal.site/api/v1/profiles/YOUR_BUSINESS_ID/invocations",
    headers={"Authorization": "Bearer gl_YOUR_API_KEY"},
    json={
        "target": {
            "featureId": "YOUR_FEATURE_ID",
            "export": "tables",
            "contractRef": "data.tables.query@1",
        },
        "operationId": "list-tables-2026-11-05-1",
        "input": {},
    },
    timeout=30,
)
receipt = response.json()
print(response.status_code, receipt["status"])

The body must be application/json in UTF-8 and at most 64 KB. Otherwise the API answers 415 (request body must be application/json), 413 (request body is too large) or 400 (request body is not valid JSON) before anything runs.

See an operation's input and result

GET /catalog/{entryRef} answers {"entry": …, "inputSchema": …, "resultSchema": …}: the catalogue entry and the operation's input and result, both as JSON Schema. The API reference pages describe the same fields in words.

Read the receipt

Every call answers with a receipt: its status, the operationId you sent, and, when it worked, the operation's output.

{
  "status": "applied",
  "operationId": "list-tables-2026-11-05-1",
  "output": { "tables": [] }
}
Status HTTP What it means
applied 200 Done. output holds the result.
pending 202 Accepted and still running.
uncertain 202 GrowLocal can't yet tell whether it took effect. Check before trying again.
requirements 409 Something must happen first; the receipt lists what.
conflict 409 The row changed since you read it: read it again, then retry with its new revision. Also the answer when an operationId you already used comes with a different body.
rejected 400, or 403 when it's a matter of access Not done. diagnostics says why.

Each item in diagnostics has a code (such as DATA.FIELD_UNKNOWN), a path into what you sent, a message, and repairBy, who can fix it: caller means your request. Errors lists the answers you're most likely to see.