Let an AI assistant read what is happening in your workspace (bookings, spaces, members, companies and invoices) using the Model Context Protocol (MCP). Access is read-only by default and always limited to your own organization. A key can optionally also be allowed to create and edit records, but only after the AI has shown the change and a person has approved it.
cwk_ and is shown only once.Endpoint: https://cowork.get200.africa/api/mcp
Authentication: Authorization: Bearer cwk_your_key_hereIf the screen says API access is not enabled, ask your platform administrator to switch on "API access" for your organization.
claude mcp add --transport http cowork https://cowork.get200.africa/api/mcp --header "Authorization: Bearer $COWORK_API_KEY"https://cowork.get200.africa/api/mcp and Authentication to No sign-in.authorization with the value Bearer cwk_your_key_here (the word Bearer, a space, then the key). Alternatively use the header name x-api-key with just the key.The Request headers section is a beta that only some Claude organizations have. If you don't see it, your account doesn't have access yet. Claude can't change a connector's headers after it's added: remove the connector and add it again.
Add a remote server with the endpoint above and the header Authorization: Bearer <key> (an X-API-Key: <key> header also works). Client configuration formats differ, so follow your client's documentation for remote HTTP servers.
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
const transport = new StreamableHTTPClientTransport(new URL("https://cowork.get200.africa/api/mcp"), {
requestInit: { headers: { Authorization: `Bearer ${process.env.COWORK_API_KEY}` } },
})
const client = new Client({ name: "my-app", version: "1.0.0" })
await client.connect(transport)
const { tools } = await client.listTools()
const result = await client.callTool({
name: "list_bookings",
arguments: { hasNotes: true, limit: 5 },
})
console.log(result.content)curl -s https://cowork.get200.africa/api/mcp \
-H "Authorization: Bearer $COWORK_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'The Content-Type and Accept headers are both required (without Accept the server answers 406). Every request is independent, so there is no session to open or close.
Each key carries one or more permissions. Read permissions only look at data. Write permissions (marked below) additionally allow the AI to change data, always through the approval step, and each one needs the reading that goes with it.
| Permission | What it allows | Tools it unlocks |
|---|---|---|
| organization:read | Organization name and currency, locations, and headline counts (members by status, upcoming bookings). | get_organization_overview, list_locations |
| spaces:read | Rooms, desks and offices with capacity and hourly, daily and monthly rates. | list_spaces |
| bookings:read | Bookings with times, status, amounts, the special notes bookers add, and requirement items. Each booking shows the booker's name (not their email or phone). | list_bookings, get_booking |
| members:read | The member directory including email, phone and job title. Also lets bookings and invoices include each person's email and be searched by email. | list_members |
| companies:read | Client companies, their booking contact and contract dates. | list_companies |
| invoices:read | Invoices, line items, payments and totals by status. Each invoice shows the billed member's name (not their email). | list_invoices, get_invoice, get_invoice_summary |
| bookings:writechanges data | Create a booking for an existing member, reschedule or edit one, or cancel one. Uses the same availability, duration and pricing rules as the app. Sends no emails.Also needs: bookings:read, spaces:read, members:read | create_booking, update_booking, cancel_booking |
| members:writechanges data | Add new members or prospects and edit their name, phone, job title, status or company. Email addresses cannot be changed.Also needs: members:read | create_member, update_member |
| companies:writechanges data | Add client companies and edit their details and booking contact.Also needs: companies:read | create_company, update_company |
| invoices:writechanges data | Create DRAFT invoices from existing bookings and edit a draft's dates and notes. It can never send, pay, void or change the amounts of an invoice.Also needs: invoices:read, bookings:read | create_draft_invoice, update_draft_invoice |
Personal contact details are protected separately: without members:read, bookings and invoices show a person's name only, and searching by email is refused.
Tools that change data (the ones marked changes data in the reference below) never change anything by being called. How a person approves depends on the key's approval mode, chosen when the key is created.
The call is checked exactly as the real change would be, then stored as a request in Settings → AI approvals. The AI is told it is waiting. An owner or admin sees what will happen and clicks Approve or Reject. Nothing the AI can send back will apply the change, so it is safe even if the AI client is set to "always allow".
{"name":"create_booking","arguments":{"spaceId":"...","memberEmail":"ann@example.com",
"startTime":"2026-11-03T09:00:00+03:00","endTime":"2026-11-03T11:00:00+03:00"}}
// -> {"status":"pending_approval","changesMade":false,"requestId":"...","summary":"Create a hourly booking of ... Total KES 2,000.00 ..."}
// later, to see what happened
{"name":"check_change_request","arguments":{"requestId":"..."}}
// -> {"status":"APPROVED","changesMade":true,"result":{"bookingId":"..."}} (or REJECTED with the reason, EXPIRED, FAILED)check_change_request only shows requests made by the same key.When a key is created with a webhook URL, Cowork sends a signed POST to it as soon as a person approves or rejects one of that key's requests, or when approving it fails the final check. The AI platform no longer needs to call check_change_request in a loop. The URL must be a public https address on the standard port. The signing secret (whsec_…) is shown once, next to the key.
POST https://your-platform.example.com/hooks/cowork
Content-Type: application/json
X-Cowork-Event: change_request.approved (or change_request.rejected, change_request.failed)
X-Cowork-Delivery: 5d0b… (unique per delivery attempt series)
X-Cowork-Signature: t=1767225600,v1=9a3f…
{"id":"evt_<requestId>","type":"change_request.approved","createdAt":"2026-11-03T09:14:02.000Z",
"data":{"requestId":"...","tool":"create_booking","summary":"...","status":"APPROVED",
"changesMade":true,"result":{"bookingId":"..."}}}Verify every message before trusting it. The signature is an HMAC-SHA256, in hex, of the timestamp, a dot and the raw request body, using your secret:
import crypto from "crypto"
function verify(rawBody: string, header: string, secret: string) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")))
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false // too old: possible replay
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex")
return v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}2xx status. Anything else, or no answer within 5 seconds, is retried (up to 3 tries in all, over about 5 seconds). The event id stays the same on retries, so treat it as idempotent.check_change_request still returns it. The failure is shown in Settings → AI approvals.check_change_request reports it as EXPIRED.Keys created with this mode work in two calls. The first call changes nothing and returns status: "needs_approval", a summary, the preview and a confirmationToken. The AI shows the summary to the person and, only if they explicitly say yes, calls the same tool again with the same arguments plus the confirmationToken. The result is status: "done".
The limit of this mode: the server can prove a preview was requested and echoed back, not that a person read it. An AI client set to "always allow" can approve on the person's behalf. Prefer approval in Cowork.
mcp:cwk_…).List endpoints return at most 100 rows per call (default 25); page with offset. Dates are ISO 8601. Amounts are returned as strings to avoid rounding.
High-level snapshot: locations, spaces, member counts by status, upcoming confirmed bookings, and bookings that carry special notes.
No parameters.
Locations (buildings/branches) of this organization.
| Parameter | Type | Description |
|---|---|---|
| limit? | integer | Max rows to return (default 25, max 100) |
| offset? | integer | Rows to skip, for paging |
Bookable spaces (rooms, desks, offices) with capacity and rates.
| Parameter | Type | Description |
|---|---|---|
| locationId? | string | Only spaces in this location |
| type? | PRIVATE_OFFICE | MEETING_ROOM | CONFERENCE_ROOM | DAY_PASS | BOARD_ROOM | MONTHLY_HOTDESK | VIRTUAL_OFFICE | PODCAST | |
| limit? | integer | Max rows to return (default 25, max 100) |
| offset? | integer | Rows to skip, for paging |
Bookings overlapping an optional time window, newest or soonest first. Each booking includes the booker's special notes (`notes`) and requirement items.
| Parameter | Type | Description |
|---|---|---|
| from? | string | ISO 8601; only bookings ending after this moment |
| to? | string | ISO 8601; only bookings starting before this moment |
| status? | CONFIRMED | CANCELLED | COMPLETED | |
| locationId? | string | |
| spaceId? | string | |
| memberEmail? | string | Exact email of the booker (requires the members:read permission) |
| hasNotes? | boolean | true = only bookings with special notes |
| search? | string | Case-insensitive text match on title and notes |
| newestFirst? | boolean | Sort by start time descending (default ascending) |
| limit? | integer | Max rows to return (default 25, max 100) |
| offset? | integer | Rows to skip, for paging |
One booking by id, including special notes and requirement items.
| Parameter | Type | Description |
|---|---|---|
| bookingId | string |
Members and contacts (name, email, phone, status, company). Tax ids and internal notes are not exposed.
| Parameter | Type | Description |
|---|---|---|
| search? | string | Case-insensitive match on name, email or company |
| status? | PROSPECT | ACTIVE | DROP_IN | FORMER | |
| companyId? | string | |
| limit? | integer | Max rows to return (default 25, max 100) |
| offset? | integer | Rows to skip, for paging |
Client companies with member counts.
| Parameter | Type | Description |
|---|---|---|
| search? | string | Case-insensitive match on company name |
| limit? | integer | Max rows to return (default 25, max 100) |
| offset? | integer | Rows to skip, for paging |
Invoices filtered by status, issue-date range, member email or company.
| Parameter | Type | Description |
|---|---|---|
| status? | DRAFT | SENT | PAID | OVERDUE | CANCELLED | PARTIALLY_PAID | |
| issuedFrom? | string | ISO 8601; issue date on or after |
| issuedTo? | string | ISO 8601; issue date before |
| memberEmail? | string | Exact email of the billed member (requires the members:read permission) |
| companyId? | string | |
| limit? | integer | Max rows to return (default 25, max 100) |
| offset? | integer | Rows to skip, for paging |
One invoice by id or invoice number, with line items and payments.
| Parameter | Type | Description |
|---|---|---|
| invoiceId? | string | |
| invoiceNumber? | string |
Invoice counts and totals grouped by status and currency for an optional issue-date range.
| Parameter | Type | Description |
|---|---|---|
| issuedFrom? | string | ISO 8601; issue date on or after |
| issuedTo? | string | ISO 8601; issue date before |
Create a confirmed booking for an existing member (or a company's representative) in a space. Uses the app's own availability, duration and pricing rules; sends no emails. Find ids with the read tools first. This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| spaceId | string | |
| startTime | string | ISO 8601 with a timezone offset, e.g. 2026-11-03T09:00:00+03:00 |
| endTime | string | ISO 8601 with a timezone offset |
| memberId? | string | Who the booking is for. Give memberId, memberEmail or companyId. |
| memberEmail? | string | Exact email of an existing member |
| companyId? | string | Book for a company; a representative member is chosen if no member is given |
| title? | string | |
| notes? | string | Special notes from the booker |
| quantity? | integer | |
| pricingType? | HOURLY | HALF_DAY | DAILY | MONTHLY | Defaults to hourly when the space has an hourly rate |
| floorId? | string | Required when the space is on several floors |
| requirementItems? | array | |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Change a confirmed booking's title, notes, times, space, quantity or requirement items. Changes that affect price are recalculated and, if the booking is on an invoice, pushed onto it. Cancelled bookings cannot be edited. This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| bookingId | string | |
| title? | any | null clears the title |
| notes? | any | null clears the notes |
| startTime? | string | ISO 8601; send together with endTime |
| endTime? | string | ISO 8601; send together with startTime |
| spaceId? | string | Move the booking to another space |
| floorId? | string | |
| quantity? | integer | |
| requirementItems? | array | Replaces the whole list |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Cancel a confirmed booking. This only sets its status to cancelled (it is not deleted) and can be reversed from the booking screen. It does not change any invoice. This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| bookingId | string | |
| reason? | string | Recorded in the audit log |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Add a new member or prospect. Refused if a member with that email already exists. This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| firstName | string | |
| lastName | string | |
| string | ||
| phone | string | |
| status? | PROSPECT | ACTIVE | DROP_IN | FORMER | Defaults to PROSPECT |
| companyId? | string | |
| jobTitle? | string | |
| locationId? | string | Required when the organization has more than one location |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Change a member's name, phone, job title or status. Email addresses and company membership cannot be changed this way (moving someone between companies also moves contracts, so that stays in the app). This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| memberId | string | |
| firstName? | string | |
| lastName? | string | |
| phone? | string | |
| jobTitle? | any | null clears it |
| status? | PROSPECT | ACTIVE | DROP_IN | FORMER | |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Add a client company. Refused if a company with the same name already exists. This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| name | string | |
| industry? | string | |
| size? | STARTUP | SMALL | MEDIUM | LARGE | ENTERPRISE | |
| website? | string | |
| description? | string | |
| bookingContactName? | string | |
| bookingContactEmail? | string | |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Change a company's details or booking contact. This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| companyId | string | |
| name? | string | |
| industry? | any | |
| size? | any | |
| website? | any | |
| description? | any | |
| bookingContactName? | any | |
| bookingContactEmail? | any | |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Create a DRAFT invoice from existing, uninvoiced bookings of one member or company. The invoice is never sent and never marked paid; a person finalizes it in the app. This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| bookingIds | array | |
| dueInDays? | integer | Days until due, default 30 |
| notes? | string | |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Change a DRAFT invoice's issue date, due date or notes. Only drafts can be edited, and amounts can never be changed this way. This is a two-step change. The first call only returns a preview and a confirmationToken and changes nothing: show the preview to the user, get their explicit approval, then call again with the same arguments plus the confirmationToken.
| Parameter | Type | Description |
|---|---|---|
| invoiceId | string | |
| issueDate? | string | ISO 8601 date |
| dueDate? | any | ISO 8601 date; null clears it |
| notes? | any | null clears the notes |
| confirmationToken? | string | Leave empty on the first call to get a preview. After the user approves the preview, call again with the same arguments and this token. |
Limits are per API key unless stated, and are enforced by the server.
| Limit | Value | What happens beyond it |
|---|---|---|
| Requests of any kind | 120 per minute | HTTP 429 with a Retry-After header |
| Rows per list call | 100 (default 25) | Use offset to page |
| Change requests waiting for approval | 20 at once | The tool call fails until a person approves or rejects some |
| New change requests | 60 per hour | The tool call fails until the hour has passed |
| How long a request can be approved | 24 hours | It shows as EXPIRED and cannot be approved |
| Changes in chat-approval mode | 30 per hour; token valid 10 minutes | The tool call fails |
| Webhook delivery | 3 tries, 5 s each | Marked failed in Settings → AI approvals; the outcome stays available via check_change_request |
| Active keys per organization | 10 (all keys together) | Creating another is refused until one is revoked |
| Time for one call | 30 seconds | The call is cut off |
Responses you may see:
| Response | Meaning |
|---|---|
| 401 | Missing, invalid, expired or revoked key, or the person who created it was deactivated. |
| 403 | API access is not enabled for the organization, or the organization is suspended. |
| 405 | Only POST is supported. |
| 429 | More than 120 requests per minute for this key. Retry after the number of seconds in the Retry-After header. |
200 with isError | The call reached the tool but failed (for example a record that does not exist, or a filter the key is not permitted to use). The message explains why. |