Build with AI

Call Workspace369 over HTTP (Builder API)

Call Workspace369 from apps built with Lovable, v0, Bolt or Replit over HTTPS and JSON, with a URL per resource, an OpenAPI spec and MCP permissions.

View as Markdown

Apps built with Lovable, v0, Bolt or Replit can't connect to an MCP server while they run. They call HTTP. The Builder API gives them the same Workspace369 tools as the MCP server, over plain HTTPS and JSON.

It runs the same tools as MCP, so it has the same permissions, approvals, audit log and usage. A call made through it is billed exactly like the same call made over MCP.

Who can use it

  • The Specialist plan and up (Specialist, Commander, Enterprise). On any lower plan (Cadet, Voyager) calls return 403 with plan_required. MCP itself works on every plan.
  • An admin must turn on AI connections for the workspace.
  • You need a personal access token (it starts with mcp_pat_). Sign-in tokens from Claude or ChatGPT don't work here.
  • The token's scopes decide what it can do. A call outside its scopes returns 403 with insufficient_scope, and details.required_scope names the scope it needs.

Keep the token on your server. Never put it in browser code or a public repo.

Base URL

https://mcp.workspace369.com/v1

The full spec is public (no token needed) at GET /v1/openapi.json. Point your code generator at it. The spec lists paths only; set the base URL https://mcp.workspace369.com in your generator.

Two ways to call

Any tool by name: POST /v1/tools/{name} with the tool's input as the JSON body. The answer is the tool's output. Keys the tool doesn't know are ignored. The tool names are in the MCP tools reference.

curl -s https://mcp.workspace369.com/v1/tools/clients_search \
  -H "Authorization: Bearer $W369_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"acme"}'

Some tools run only inside the app and can't be called here (batch_apply, and the workflow tools automations_create_draft, automations_activate, automations_retry). They return 404 with tool_not_found.

Resource paths: shortcuts onto the same tools.

Method and pathToolScope
GET /v1/clients, GET /v1/clients/{id}clients_search, clients_getclients:read
POST /v1/clients, PATCH /v1/clients/{id}clients_create, clients_updateclients:write
GET /v1/proposals, GET /v1/proposals/{id}proposals_search, proposals_getproposals:read
POST /v1/proposals, PATCH /v1/proposals/{id}proposals_create_draft, proposals_update_draftproposals:write
POST /v1/proposals/{id}/sendproposals_sendproposals:write
GET /v1/invoices, GET /v1/invoices/{id}invoices_search, invoices_getinvoices:read
POST /v1/invoices, PATCH /v1/invoices/{id}invoices_create_draft, invoices_update_draftinvoices:write
POST /v1/invoices/{id}/sendinvoices_sendinvoices:write
GET /v1/contracts, GET /v1/contracts/{id}contracts_search, contracts_getcontracts:read
POST /v1/contractscontracts_create_draftcontracts:write
POST /v1/contracts/{id}/sendcontracts_sendcontracts:send
GET /v1/projects, GET /v1/projects/{id}projects_search, projects_getprojects:read
POST /v1/projects, PATCH /v1/projects/{id}projects_create, projects_updateprojects:write
GET /v1/tasks, GET /v1/tasks/{id}tasks_search, tasks_get (also needs ?board_id=)tasks:read
POST /v1/tasks, PATCH /v1/tasks/{id}tasks_create, tasks_updatetasks:write
GET /v1/requests, GET /v1/requests/{id}requests_search, requests_getrequests:read
PATCH /v1/requests/{id}requests_update_statusrequests:write
GET /v1/calendar/eventscalendar_list_eventscalendar:read
POST /v1/calendar/events, PATCH /v1/calendar/events/{id}calendar_create_event, calendar_update_eventcalendar:write

There is no PATCH for contracts, no POST for requests, and no DELETE anywhere. Use POST /v1/tools/{name} for any tool without a shortcut.

On GET, put the tool's arguments in the query string. Numbers and true/false are read as such, and a list is comma-separated. The {id} in the path always wins over an id in the body or query.

A successful call returns 200 with the tool's output as JSON. A get for an id that doesn't exist also returns 200, with the record as null, so check for it.

Sends wait for approval

Sending a proposal, contract, invoice or message returns 202 with the approval details. Nothing goes out until a person approves it in the app. A few other actions that reach a customer are held the same way (bulk messages, contract reminders and voids, replying to a request, pay links).

{
  "held_for_approval": true,
  "approval_id": "2b89220a51485f343f1ae00407384707",
  "approval_url": "https://app.workspace369.com/settings/approvals/2b89220a...",
  "approval_expires_at": "2026-10-21T06:24:27.070Z"
}

Show "awaiting approval" in your app for a 202. To check the result later, call the approvals_get tool with the approval_id. If the person who connected turned on "send without asking", the send runs at once and you get 200.

Pass a stable idempotency_key on sends so a retry doesn't send twice.

Errors

Every error has the same shape: {"error":{"code","message","details"}}.

StatusCodeMeaning
400invalid_jsonThe body isn't valid JSON. Some malformed bodies are rejected before they reach the API and get a plain 400 Bad Request with no JSON body, so treat any 400 as "fix the request".
401unauthorizedMissing or invalid token.
402usage_exhaustedThe workspace is out of Tokens.
403insufficient_scopeThe token lacks a scope; details.required_scope names it.
403forbiddendetails.reason is permission_denied (the person lacks the permission), mcp_not_enabled (AI connections are off) or app_not_approved.
403plan_requiredThe workspace is below the Specialist plan.
404not_foundNo such path. A record that doesn't exist is not an error: a get returns 200 with null (for example {"client":null}).
404tool_not_foundNo such tool, or one that only runs inside the app.
409conflictThe record isn't in a state this call can act on.
413payload_too_largeThe body is over 1 MB.
422invalid_requestThe input failed validation; details.issues lists each problem.
422tool_errorThe input was valid but the tool refused it; message says why.
429rate_limitedToo many requests. Wait for Retry-After seconds (also in details.retry_after_seconds). Also returned when too many approvals are waiting.
500internal_errorSomething failed on our side. Not charged.
503service_unavailable, tool_disabledTry again shortly. tool_disabled means that one tool is switched off for now.

Rate limits: 120 requests a minute per token, 600 a minute per workspace. The limit is shared with your MCP calls.

Was this article useful?

Your answer helps us decide what to clarify next.