Metis Events API

Connect an event-management, catering or CRM system to Metis Room Planner. Your software asks for a floorplan; the venue's own team draws it properly in Metis; you get the finished plan back as a PDF to attach to the BEO or send to the customer.

What this API is for

Venues already run their events in your software. What they don't have is a quick way to turn a booking into a room plan without re-typing everything into a separate drawing tool. That's the gap this API closes.

The unit of work is the plan request. You send a room, a layout style, guest numbers and the event details. Metis creates the plan in the venue's account and puts it in front of their team, who know where the pillar is and which door the band loads in through. When they mark it complete, the plan is rendered to a PDF and a PNG that you can pull straight back into the booking.

Everything else — listing venues and rooms, keeping occasions in step, opening Metis for a person — exists to support that loop.

Rooms are structure, not furniture. A Metis room holds walls, doors, windows, pillars and fixed features, with no tables or chairs. Furniture belongs to the event, so every plan starts from the empty room and is laid out for the numbers on the night.

The main flow

  1. Create the occasion POST /v1/occasions — the event, its date, type and guest count.
  2. Pick a room GET /v1/venues then GET /v1/venues/{venueId}/rooms. Cache these; they change rarely.
  3. Request the plan POST /v1/occasions/{occasionId}/plan-requests with the room, layout style, numbers and times. Status: requested.
  4. The venue does the work Their team opens the request in Metis, finishes the plan and marks it complete. Status: in_progresscompleted.
  5. Collect the result Poll GET /v1/plan-requests?status=completed, then take the newest entry in versions and download the PDF — or store its signed pdfShareUrl.
  6. Changes happen PATCH the plan request when numbers or times move. Metis shows the venue's team exactly what changed and the request goes back to changes_requested, producing a new version when they finish.

Getting access

There are two kinds of credential, and an integration usually ends up holding both.

Either way the key is shown once, when it is created. Metis stores only a hash of it and cannot show it to you again — if it's lost, the customer revokes it and issues a new one.

Authentication

Every call carries your key as a bearer token over HTTPS:

Authorization: Bearer mk_live_YOUR_KEY_HERE

Keys beginning mk_test_ act on test data; mk_live_ keys act on the real account. After the prefix come 40 random characters — never parse them, and never derive anything from them.

A key belongs to a connection: one venue business's Metis account. Most keys are pinned to a single connection and you can ignore this entirely. If Metis has given you a key that serves several accounts, name the one you mean in a header:

Metis-Connection: con_8fK2mQpL4xRz9TbW3vNcYh7J

Each key is granted only the scopes it needs. A call outside them returns 403 insufficient_scope — it is not a bug to fix by retrying, it means the customer needs to grant more when issuing the key.

ScopeLets you
venues:readList venues, rooms and layout styles.
occasions:readRead occasions.
occasions:writeCreate and update occasions.
floorplans:readRead floorplans and plan requests, and download PDFs and PNGs.
floorplans:writeRaise, change and cancel plan requests.
links:createCreate links that open Metis for a person.

GET /v1/connection tells you which account a key acts on and which scopes it holds. It's the right call behind a "Test connection" button.

Keeping keys safe

An API key is a password to a venue's data. Treat it as one.

Quick start

Set two environment variables and check the key works:

export METIS_BASE="https://metisroomplanner.com/MetisEventsApi"
export METIS_API_KEY="mk_test_…"          # from Metis, or from your customer's Integrations screen
export METIS_CONNECTION="con_…"           # only if your key is not pinned to one account

curl -sS "$METIS_BASE/v1/connection" -H "Authorization: Bearer $METIS_API_KEY"

The samples further down assume one small helper. In Node:

const BASE = process.env.METIS_BASE;

async function metisRaw(path, { method = "GET", body, idempotencyKey, ifMatch } = {}) {
  const headers = {
    "Authorization": `Bearer ${process.env.METIS_API_KEY}`,
    "Metis-Connection": process.env.METIS_CONNECTION,
  };
  if (body) headers["Content-Type"] = method === "PATCH"
    ? "application/merge-patch+json"
    : "application/json";
  if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
  if (ifMatch) headers["If-Match"] = ifMatch;

  return fetch(BASE + path, { method, headers, body: body ? JSON.stringify(body) : undefined });
}

async function metis(path, options) {
  const response = await metisRaw(path, options);
  if (!response.ok) {
    // Errors are RFC 9457 problem documents. Branch on `code`, never on the message.
    const problem = await response.json();
    throw Object.assign(new Error(problem.title), problem, { status: response.status });
  }
  return response.status === 204 ? null : response.json();
}

And in C#:

var http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable("METIS_BASE") + "/") };
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("METIS_API_KEY"));
http.DefaultRequestHeaders.Add("Metis-Connection", Environment.GetEnvironmentVariable("METIS_CONNECTION"));

Requests and responses

Pagination

Lists take limit (1–100, default 25) and an opaque cursor. Follow nextCursor until hasMore is false; the same next page is also given as an RFC 8288 Link: <…>; rel="next" header. Never build a cursor yourself — an invalid one returns 400 invalid_cursor.

Idempotency

Send Idempotency-Key: <uuid> on every POST. If the connection drops before you see the response, retry with the same key: Metis returns the original response and adds Idempotent-Replayed: true rather than doing the work twice. Keys are remembered for 24 hours.

Reusing a key with a different body returns 409 idempotency_key_reused — that's a bug in your key generation, not a transient failure. Generate one key per logical operation and keep it with the retry.

ETags and updates

Single resources come back with an ETag. Send it as If-None-Match on a read to get a cheap 304 Not Modified, or as If-Match on a PATCH so your update is rejected with 412 precondition_failed if someone changed the resource first. On a 412, re-read, re-apply your change and retry.

Updates are JSON Merge Patch (RFC 7396): send only the fields you're changing. null clears a field that is allowed to be empty.

Rate limits

Authenticated partners get 600 requests per 60 seconds, shared across all of that partner's keys and connections. Requests without a valid key are limited far more tightly, per IP address. Over the limit you get 429 rate_limited with a Retry-After header — wait that long, then retry with exponential backoff and jitter.

Don't poll harder than you need to. Once every few minutes per connection is plenty for GET /v1/plan-requests?status=completed; a venue takes minutes or hours to finish a plan, not seconds.

Errors

Errors are RFC 9457 problem documents (application/problem+json) with a stable, machine-readable code. Branch on code, never on title — titles are written for humans and may be reworded.

{
  "type": "https://metisroomplanner.com/docs/events-api/errors#validation_failed",
  "title": "One or more request parameters are invalid.",
  "status": 400,
  "code": "validation_failed",
  "errors": { "guestCount": ["Must be between 0 and 100000."] },
  "traceId": "3579c94390d9066755d9d544c9cbccd8"
}
StatusCodeWhat to do
400validation_failedRead errors; it's keyed by field name. Fix and resend.
400invalid_cursorStart the list again from the first page.
400connection_requiredYour key serves several accounts — send Metis-Connection.
401invalid_api_keyStop. Ask your user to re-issue the key in Metis.
403insufficient_scopeThe key wasn't granted this scope. Ask for a key that has it.
403connection_not_foundThe connection is unknown or revoked.
404resource_not_foundAlso returned for resources that belong to another account.
409duplicate_external_referenceYou already created this. Fetch it instead.
409idempotency_key_reusedSame key, different body. Fix your key generation.
409idempotency_request_in_progressThe first attempt is still running. Wait and retry.
409render_pendingThe PDF isn't ready. Retry after Retry-After.
412precondition_failedRe-read, re-apply, retry.
415unsupported_media_typeSet Content-Type correctly.
422plan_allowance_reachedThe venue's subscription is out of plans. Retrying won't help — tell your user.
422subscription_inactiveThe venue's Metis subscription isn't active.
429rate_limitedBack off for Retry-After seconds.
500internal_errorSafe to retry an idempotent request with backoff. Quote the traceId.
503planner_unavailableRetry with the same Idempotency-Key.

Connection

Check your credentials

GET/v1/connectionno scope needed

Returns the venue account your key is acting on and the scopes it was granted. Call it once at start-up, or whenever a customer pastes a new key into your settings screen, to prove the key works before you try anything else. It needs no scope of its own.

Returns

A connection object.

Example response

{
  "id": "con_8fK2mQpL4xRz9TbW3vNcYh7J",
  "partner": {
    "name": "Caterease"
  },
  "account": {
    "name": "Grand Hotel Group"
  },
  "scopes": [
    "venues:read",
    "occasions:read",
    "occasions:write",
    "floorplans:read",
    "floorplans:write",
    "links:create"
  ]
}

Notes

  • This is the call to use in a 'Test connection' button.

Example request

curl -sS "$METIS_BASE/v1/connection" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Venues and rooms

List venues

GET/v1/venuesscope venues:read

The venues on the connected account — the buildings or sites the customer runs events in. Use it to populate a venue picker. Rooms live underneath a venue.

Query parameters

NameTypeDescription
querystring
optional
Case-insensitive substring match on the venue or room name.
limitinteger
optional default 25
How many items to return, 1–100.
cursorstring
optional
The nextCursor from the previous page. Opaque — pass it back unchanged.

Returns

A page of venues, each with the number of rooms it has.

Example response

{
  "data": [
    {
      "id": "ven_8",
      "name": "Grand Hotel",
      "roomCount": 6
    }
  ],
  "hasMore": false,
  "nextCursor": null
}

Example request

curl -sS "$METIS_BASE/v1/venues?limit=25" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Retrieve a venue

GET/v1/venues/{venueId}scope venues:read

One venue by id. Useful when you store the venue id against your own site record and want to show its current name.

Path parameters

NameTypeDescription
venueIdstring
required
A venue id from GET /v1/venues, e.g. ven_8.

Returns

A venue object.

Example response

{
  "id": "ven_8",
  "name": "Grand Hotel",
  "roomCount": 6
}

Example request

curl -sS "$METIS_BASE/v1/venues/ven_8" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

List a venue's rooms

GET/v1/venues/{venueId}/roomsscope venues:read

Every room at a venue, with its dimensions and any layouts the customer has already saved for it. A room is structure only — walls, doors, windows, pillars and fixed features, with no furniture; furniture is laid out per event. This is the call that feeds your room picker, and the room id is what you send when you ask for a plan.

Path parameters

NameTypeDescription
venueIdstring
required
A venue id from GET /v1/venues.

Returns

All of the venue's rooms in a single page.

Example response

{
  "data": [
    {
      "id": "room_26",
      "venueId": "ven_8",
      "name": "Ballroom",
      "structureVersion": 3,
      "dimensions": {
        "widthCm": 1800,
        "lengthCm": 2400,
        "ceilingHeightCm": 420
      },
      "layouts": [
        {
          "id": "lay_58",
          "name": "Cabaret Banquet, Dancefloor & Disco",
          "updatedAt": "2026-08-30T14:02:11Z"
        }
      ]
    }
  ],
  "hasMore": false,
  "nextCursor": null
}

Notes

  • structureVersion tells you which published version of the room a plan was built from. It changes when the venue re-measures or re-draws the room.

Example request

curl -sS "$METIS_BASE/v1/venues/ven_8/rooms" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Retrieve a room

GET/v1/rooms/{roomId}scope venues:read

One room by id, with the same detail as the list above. Handy for refreshing a room you have stored without re-reading the whole venue.

Path parameters

NameTypeDescription
roomIdstring
required
A room id, e.g. room_26.

Returns

A room object.

Example response

{
  "id": "room_26",
  "venueId": "ven_8",
  "name": "Ballroom",
  "structureVersion": 3,
  "dimensions": {
    "widthCm": 1800,
    "lengthCm": 2400,
    "ceilingHeightCm": 420
  },
  "layouts": []
}

Example request

curl -sS "$METIS_BASE/v1/rooms/room_26" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Occasions

List occasions

GET/v1/occasionsscope occasions:read

The events held at the customer's venues that your system has told Metis about, earliest date first. Use it to reconcile after an outage, or to show what Metis already knows.

Query parameters

NameTypeDescription
yearinteger
optional
Only occasions whose date falls in this calendar year.
limitinteger
optional default 25
How many items to return, 1–100.
cursorstring
optional
The nextCursor from the previous page. Opaque — pass it back unchanged.

Returns

A page of occasions ordered by date.

Example response

{
  "data": [
    {
      "id": "occ_42",
      "name": "Smith-Jones Wedding",
      "date": "2027-06-12",
      "type": "wedding",
      "guestCount": 150,
      "venueId": "ven_8",
      "externalReference": "CRM-10492",
      "createdBy": {
        "name": "Alex Planner",
        "email": "[email protected]"
      },
      "createdAt": "2026-09-17T08:34:06Z",
      "updatedAt": "2026-09-17T08:34:06Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}

Example request

curl -sS "$METIS_BASE/v1/occasions?year=2027&limit=25" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Create an occasion

POST/v1/occasionsscope occasions:write

Tells Metis about an event in your system — the wedding, conference or dinner the plan will be for. This is normally the first call you make for a booking; everything else (plan requests, floorplans) hangs off the occasion it returns. Put your own booking reference in externalReference so you can find it again.

Headers

HeaderTypeDescription
Idempotency-Keystring
optional
A unique value per logical operation (a UUID is ideal). Retrying with the same key within 24 hours returns the original response instead of doing the work twice. Strongly recommended for every POST.

Body fields

FieldTypeDescription
namestring
required
What the event is called, as your customer would recognise it. 1–300 characters.
datedate
required
The event date, YYYY-MM-DD.
typeenum
required
One of wedding, corporate, not_for_profit, bar_bat_mitzvah, dinner_party, other.
guestCountinteger
required
Expected number of guests, 0–100000.
venueIdstring
optional
Which venue it's at. Optional, but set it if you know — it filters the rooms the venue's team sees.
externalReferencestring
optional
Your identifier for this event (booking number, CRM id). Up to 200 characters, and unique among your connection's occasions.
createdByobject
optional
Who raised it in your system: { name, email }. Shown to the venue's team so they know who to ask.

Returns

201 Created with the occasion, a Location header and an ETag.

Errors worth handling

409 duplicate_external_referenceYou've already created an occasion with that reference. Fetch it instead of creating another.

Notes

  • Retrying with the same Idempotency-Key returns the original occasion and the header Idempotent-Replayed: true, so a network timeout never creates a duplicate.

Example request

curl -sS -X POST "$METIS_BASE/v1/occasions" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "Smith-Jones Wedding", "date": "2027-06-12", "type": "wedding", "guestCount": 150, "venueId": "ven_8", "externalReference": "CRM-10492", "createdBy": {"name": "Alex Planner", "email": "[email protected]"}}'

Retrieve an occasion

GET/v1/occasions/{occasionId}scope occasions:read

One occasion by id, with an ETag you'll need if you want to update it safely.

Path parameters

NameTypeDescription
occasionIdstring
required
An occasion id, e.g. occ_42.

Returns

An occasion object.

Example response

{
  "id": "occ_42",
  "name": "Smith-Jones Wedding",
  "date": "2027-06-12",
  "type": "wedding",
  "guestCount": 150,
  "venueId": "ven_8",
  "externalReference": "CRM-10492",
  "createdBy": {
    "name": "Alex Planner",
    "email": "[email protected]"
  },
  "createdAt": "2026-09-17T08:34:06Z",
  "updatedAt": "2026-09-17T08:34:06Z"
}

Notes

  • Send If-None-Match: "<etag>" to get a cheap 304 Not Modified when nothing has changed.

Example request

curl -sS "$METIS_BASE/v1/occasions/occ_42" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Update an occasion

PATCH/v1/occasions/{occasionId}scope occasions:write

Changes an event Metis already knows about — the date moved, the numbers went up, it switched venue. Send only the fields that changed (JSON Merge Patch, RFC 7396).

Path parameters

NameTypeDescription
occasionIdstring
required
An occasion id.

Headers

HeaderTypeDescription
If-Matchstring
optional
The ETag you last read. The update fails with 412 if someone changed the resource in the meantime.

Body fields

FieldTypeDescription
namestring
optional
1–300 characters. Cannot be cleared.
datedate
optional
YYYY-MM-DD. Cannot be cleared.
typeenum
optional
As on create. Cannot be cleared.
guestCountinteger
optional
0–100000. Cannot be cleared.
venueIdstring
optional
Send null to clear it.
externalReferencestring
optional
Send null to clear it.

Returns

The updated occasion with a new ETag.

Errors worth handling

412 precondition_failedSomeone changed the occasion after the ETag you sent. Re-read it, re-apply your change and try again.

Notes

  • The request Content-Type may be application/merge-patch+json or plain application/json.
  • Omitting If-Match is allowed — it just means last write wins.

Example request

curl -sS -X PATCH "$METIS_BASE/v1/occasions/occ_42" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -H 'If-Match: "the-etag-you-last-read"' \
  -H "Content-Type: application/merge-patch+json" \
  -d '{"guestCount": 165, "date": "2027-06-19"}'

Plan requests

List layout styles

GET/v1/layout-stylesscope venues:read

The room layouts Metis can generate — banquet rounds, cabaret, theatre, classroom and so on — with the options each one takes and the extras (top table, dance floor, stage) it supports. Read this to build your layout picker instead of hard-coding the list, because styles get added.

Returns

Every layout style in one page.

Example response

{
  "data": [
    {
      "key": "banquet",
      "name": "Banquet (round tables)",
      "generatesFurniture": true,
      "options": [
        {
          "name": "tableSize",
          "type": "integer",
          "allowed": [
            8,
            10,
            12
          ],
          "default": 10
        }
      ],
      "extras": [
        "topTable",
        "danceFloor",
        "stage"
      ]
    }
  ],
  "hasMore": false,
  "nextCursor": null
}

Notes

  • generatesFurniture: false (e.g. reception) means Metis prepares the room but the venue's team places the furniture by hand.

Example request

curl -sS "$METIS_BASE/v1/layout-styles" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Request a floorplan

POST/v1/occasions/{occasionId}/plan-requestsscope floorplans:write

The heart of the integration. You send the room, the layout style and the numbers; Metis creates the plan in the venue's account and puts it on their to-do list. Someone at the venue opens it, finishes it properly and marks it complete — and you get back a PDF and a PNG to attach to your BEO or customer proposal. Leave layoutStyle out and Metis just opens the empty room for them.

Path parameters

NameTypeDescription
occasionIdstring
required
The occasion this plan is for.

Headers

HeaderTypeDescription
Idempotency-Keystring
optional
A unique value per logical operation (a UUID is ideal). Retrying with the same key within 24 hours returns the original response instead of doing the work twice. Strongly recommended for every POST.

Body fields

FieldTypeDescription
roomIdstring
required
The room to plan, from GET /v1/venues/{venueId}/rooms.
layoutStyleenum
optional
A key from GET /v1/layout-styles, or null for an empty room.
layoutOptionsobject
optional
Options for that style, e.g. {"tableSize": 10, "danceFloor": true}. Values are whole numbers or true/false.
guestCountinteger
optional
Guests to lay out for. Defaults to the occasion's guest count.
startTimestring
optional
Venue local time, HH:mm.
endTimestring
optional
Venue local time, HH:mm.
notesstring
optional
Anything the venue's team should know. Up to 4000 characters — they read this.
contactobject
optional
Who to ask about it: { name, email, phone }.
externalReferencestring
optional
Your reference, e.g. the BEO number. It's used in the download file names, so it's worth setting.

Returns

201 Created with the plan request in status requested.

Errors worth handling

422 plan_allowance_reachedThe venue's Metis subscription has no plans left this period. Tell your user to contact the venue — retrying won't help.
422 subscription_inactiveThe venue's Metis subscription isn't active.
503 planner_unavailableMetis couldn't be reached. Retry with the same Idempotency-Key after Retry-After seconds.

Notes

  • Each plan request uses one of the venue's plan allowance. Don't create speculative requests.
  • Room structures themselves never count against the allowance — only the plans made from them.

Example request

curl -sS -X POST "$METIS_BASE/v1/occasions/occ_42/plan-requests" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"roomId": "room_26", "layoutStyle": "banquet", "layoutOptions": {"tableSize": 10, "danceFloor": true}, "guestCount": 120, "startTime": "19:00", "endTime": "23:30", "notes": "Top table for 12 on the stage side.", "contact": {"name": "Alex Planner", "email": "[email protected]", "phone": "+44 1234 567890"}, "externalReference": "BEO-7781"}'

List plan requests

GET/v1/plan-requestsscope floorplans:read

Your open and finished plan requests, oldest first. Poll this with ?status=completed to pick up plans the venue has finished — that's the simplest way to keep your side in step until webhooks arrive.

Query parameters

NameTypeDescription
statusenum
optional
requested, in_progress, completed, changes_requested or cancelled.
occasionIdstring
optional
Only requests for this occasion.
limitinteger
optional default 25
How many items to return, 1–100.
cursorstring
optional
The nextCursor from the previous page. Opaque — pass it back unchanged.

Returns

A page of plan requests. warnings is only filled in when you retrieve a single request.

Example response

{
  "data": [
    {
      "id": "pr_12",
      "occasionId": "occ_42",
      "roomId": "room_26",
      "status": "completed",
      "currentVersion": 2,
      "externalReference": "BEO-7781"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}

Notes

  • Poll politely — once every few minutes per connection is plenty. See Rate limits.

Example request

curl -sS "$METIS_BASE/v1/plan-requests?status=completed&occasionId=occ_42" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Retrieve a plan request

GET/v1/plan-requests/{planRequestId}scope floorplans:read

The full state of one request: where it's got to, what the venue's team has been told, any warnings raised while laying out the furniture, and every completed version with fresh download links. This is the call you make when your user opens the booking.

Path parameters

NameTypeDescription
planRequestIdstring
required
A plan request id, e.g. pr_12.

Returns

The plan request, its versions and their links.

Example response

{
  "id": "pr_12",
  "occasionId": "occ_42",
  "roomId": "room_26",
  "floorplanId": "fp_118",
  "status": "completed",
  "statusChangedAt": "2026-09-17T15:12:44Z",
  "layoutStyle": "banquet",
  "layoutOptions": {
    "tableSize": 10,
    "danceFloor": true
  },
  "guestCount": 120,
  "startTime": "19:00",
  "endTime": "23:30",
  "notes": "Top table for 12 on the stage side.",
  "contact": {
    "name": "Alex Planner",
    "email": "[email protected]",
    "phone": "+44 1234 567890"
  },
  "externalReference": "BEO-7781",
  "warnings": [
    {
      "code": "capacity_shortfall",
      "message": "Seated 110 of 120 guests; the venue's team will adjust."
    }
  ],
  "pendingChanges": [],
  "currentVersion": 1,
  "versions": [
    {
      "version": 1,
      "completedAt": "2026-09-17T15:12:44Z",
      "renderStatus": "rendered",
      "renderedAt": "2026-09-17T15:13:02Z",
      "pdfUrl": "https://metisroomplanner.com/MetisEventsApi/v1/plan-requests/pr_12/versions/1/pdf",
      "pngUrl": "https://metisroomplanner.com/MetisEventsApi/v1/plan-requests/pr_12/versions/1/png",
      "pdfShareUrl": "https://metisroomplanner.com/MetisEventsApi/files/pr_12/1/BEO-7781-floorplan-v1.pdf?e=1789..&s=6f21..",
      "pngShareUrl": "https://metisroomplanner.com/MetisEventsApi/files/pr_12/1/BEO-7781-floorplan-v1.png?e=1789..&s=a03c.."
    }
  ],
  "plannerUrl": "https://metisroomplanner.com/metisroomplanner/planner/?plan=...",
  "createdAt": "2026-09-17T09:02:10Z",
  "updatedAt": "2026-09-17T15:13:02Z"
}

Notes

  • status moves requestedin_progresscompleted, and back to changes_requested if you change the brief afterwards. cancelled is final.
  • currentVersion is 0 until the first time the venue completes the plan.
  • Share links expire after 30 days; retrieve the plan request again for fresh ones.

Example request

curl -sS "$METIS_BASE/v1/plan-requests/pr_12" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Change a plan request

PATCH/v1/plan-requests/{planRequestId}scope floorplans:write

The numbers changed, the dance floor is out, the times moved. Send only what changed and Metis shows the venue's team exactly which fields differ from the plan they already drew, moving the request to changes_requested if they'd finished it. The room can't be changed — cancel and raise a new request instead.

Path parameters

NameTypeDescription
planRequestIdstring
required
A plan request id.

Headers

HeaderTypeDescription
If-Matchstring
optional
The ETag you last read. The update fails with 412 if someone changed the resource in the meantime.

Body fields

FieldTypeDescription
layoutStyleenum
optional
A different style, or null for an empty room.
layoutOptionsobject
optional
Replaces the options wholesale. null clears them.
guestCountinteger
optional
0–100000.
startTimestring
optional
HH:mm or null.
endTimestring
optional
HH:mm or null.
notesstring
optional
Up to 4000 characters, or null.
contactobject
optional
{ name, email, phone }, or null.
externalReferencestring
optional
Up to 200 characters, or null.

Returns

The updated plan request, including the new pendingChanges entry.

Errors worth handling

409 plan_request_cancelledA cancelled request can't be changed.
412 precondition_failedIt changed after the ETag you sent.

Notes

  • Changing layoutStyle without also sending layoutOptions drops options that don't apply to the new style.

Example request

curl -sS -X PATCH "$METIS_BASE/v1/plan-requests/pr_12" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -H 'If-Match: "the-etag-you-last-read"' \
  -H "Content-Type: application/merge-patch+json" \
  -d '{"guestCount": 140, "layoutOptions": {"tableSize": 12, "danceFloor": true}}'

Cancel a plan request

POST/v1/plan-requests/{planRequestId}/cancelscope floorplans:write

The booking fell through. The venue's team sees the request as cancelled and stops work; the plan itself stays in their Metis account. Calling it twice is harmless.

Path parameters

NameTypeDescription
planRequestIdstring
required
A plan request id.

Returns

The cancelled plan request.

Example response

{
  "id": "pr_12",
  "status": "cancelled",
  "statusChangedAt": "2026-09-18T11:00:03Z"
}

Example request

curl -sS -X POST "$METIS_BASE/v1/plan-requests/pr_12/cancel" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Download a version as PDF

GET/v1/plan-requests/{planRequestId}/versions/{version}/pdfscope floorplans:read

The finished plan as an A4 landscape PDF with a title block — the thing you attach to the BEO or send to the customer. Authenticated with your API key, so use it for server-side fetches; for a link you can store or email, use the version's pdfShareUrl instead.

Path parameters

NameTypeDescription
planRequestIdstring
required
A plan request id.
versioninteger
required
Which completed version, starting at 1.

Returns

200 with application/pdf.

Errors worth handling

409 render_pendingThe PDF is still being produced. Wait Retry-After seconds and ask again.
409 render_failedMetis couldn't render this version. Contact support with the traceId.

Example request

curl -sS "$METIS_BASE/v1/plan-requests/pr_12/versions/1/pdf" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -o plan-v1.pdf

Download a version as PNG

GET/v1/plan-requests/{planRequestId}/versions/{version}/pngscope floorplans:read

The same page as a 1754×1240 image — good for a thumbnail in your booking screen or for embedding in an email.

Path parameters

NameTypeDescription
planRequestIdstring
required
A plan request id.
versioninteger
required
Which completed version, starting at 1.

Returns

200 with image/png.

Errors worth handling

409 render_pendingStill rendering; retry after Retry-After seconds.

Example request

curl -sS "$METIS_BASE/v1/plan-requests/pr_12/versions/1/png" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -o plan-v1.png

Floorplans

List an occasion's floorplans

GET/v1/occasions/{occasionId}/floorplansscope floorplans:read

Every floorplan attached to an occasion, including ones the venue's team created themselves in Metis rather than through you.

Path parameters

NameTypeDescription
occasionIdstring
required
An occasion id.

Returns

The occasion's floorplans in a single page.

Example response

{
  "data": [
    {
      "id": "fp_118",
      "occasionId": "occ_42",
      "name": "Smith-Jones Wedding — Ballroom",
      "roomId": "room_26",
      "externalReference": "BEO-7781",
      "isShared": false,
      "thumbnailUrl": null,
      "updatedAt": "2026-09-17T15:12:44Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}

Example request

curl -sS "$METIS_BASE/v1/occasions/occ_42/floorplans" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Retrieve a floorplan

GET/v1/floorplans/{floorplanId}scope floorplans:read

One floorplan by id.

Path parameters

NameTypeDescription
floorplanIdstring
required
A floorplan id, e.g. fp_118.

Returns

A floorplan object.

Example response

{
  "id": "fp_118",
  "occasionId": "occ_42",
  "name": "Smith-Jones Wedding — Ballroom",
  "roomId": "room_26",
  "externalReference": "BEO-7781",
  "isShared": false,
  "thumbnailUrl": null,
  "updatedAt": "2026-09-17T15:12:44Z"
}

Example request

curl -sS "$METIS_BASE/v1/floorplans/fp_118" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION"

Create a floorplan from a saved layout Preview — returns 501 today

POST/v1/occasions/{occasionId}/floorplansscope floorplans:write

Copies one of the customer's own saved layouts for a room straight into the occasion, skipping the request-and-complete cycle. Use it when the venue already has exactly the plan they want. To start from an empty room and have Metis lay it out, use a plan request instead.

Path parameters

NameTypeDescription
occasionIdstring
required
An occasion id.

Headers

HeaderTypeDescription
Idempotency-Keystring
optional
A unique value per logical operation (a UUID is ideal). Retrying with the same key within 24 hours returns the original response instead of doing the work twice. Strongly recommended for every POST.

Body fields

FieldTypeDescription
layoutIdstring
required
A layout id from GET /v1/rooms/{roomId}.
namestring
optional
Defaults to the occasion name and date.
externalReferencestring
optional
Your reference; unique within the occasion.

Returns

201 Created with the floorplan.

Notes

  • Counts toward the venue's Metis plan allowance.

Example request

curl -sS -X POST "$METIS_BASE/v1/occasions/occ_42/floorplans" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"layoutId": "lay_58", "name": "Smith-Jones Wedding — Ballroom", "externalReference": "BEO-7781"}'

Create a link to an occasion Preview — returns 501 today

POST/v1/occasions/{occasionId}/linksscope links:create

A short-lived URL that opens the occasion in Metis for a person — put it behind an 'Open in Metis' button in your UI.

Path parameters

NameTypeDescription
occasionIdstring
required
An occasion id.

Body fields

FieldTypeDescription
purposeenum
required
view or edit.

Returns

A link with an expiry.

Example response

{
  "url": "https://metisroomplanner.com/metisroomplanner/planner/?occasion=...",
  "expiresAt": "2026-09-18T12:00:00Z"
}

Example request

curl -sS -X POST "$METIS_BASE/v1/occasions/occ_42/links" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -H "Content-Type: application/json" \
  -d '{"purpose": "edit"}'

Create a link that opens a floorplan Preview — returns 501 today

POST/v1/floorplans/{floorplanId}/linksscope links:create

edit opens the Metis planner — the person must be a signed-in member of the venue's account. view opens a read-only 2D/3D review page.

Path parameters

NameTypeDescription
floorplanIdstring
required
A floorplan id.

Body fields

FieldTypeDescription
purposeenum
required
view or edit.

Returns

A link with an expiry.

Example request

curl -sS -X POST "$METIS_BASE/v1/floorplans/fp_118/links" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -H "Content-Type: application/json" \
  -d '{"purpose": "view"}'

Create a shareable review link Preview — returns 501 today

POST/v1/floorplans/{floorplanId}/share-linksscope links:create

A link anyone can open without a Metis account, to walk round the plan in 2D and 3D until it expires. Good for sending to the customer. Requires a Metis subscription that includes sharing.

Path parameters

NameTypeDescription
floorplanIdstring
required
A floorplan id.

Headers

HeaderTypeDescription
Idempotency-Keystring
optional
A unique value per logical operation (a UUID is ideal). Retrying with the same key within 24 hours returns the original response instead of doing the work twice. Strongly recommended for every POST.

Body fields

FieldTypeDescription
expiresInHoursinteger
optional default 72
1–720 hours.

Returns

A link with an expiry.

Example request

curl -sS -X POST "$METIS_BASE/v1/floorplans/fp_118/share-links" \
  -H "Authorization: Bearer $METIS_API_KEY" \
  -H "Metis-Connection: $METIS_CONNECTION" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"expiresInHours": 168}'

Coming from Prismm or AllSeated?

If you already integrate with Prismm (formerly AllSeated), Metis exposes a separate compatibility surface under /prismm-api/ that mirrors those calls and their response envelopes, so an existing integration can point at Metis by changing the base URL and the credentials. It is a migration aid: new work should use /v1, which is where new capability lands. Ask Metis for the compatibility contract (/openapi/prismm-compat.yaml) if you need it.

Versioning

The version is in the path (/v1). Within v1 we will add endpoints, add optional request fields and add response properties — so write a tolerant client. We will not remove or rename anything, change a type, or make an optional field required without a new version and notice.

The machine-readable contract is OpenAPI 3.1 at /openapi/v1.yaml, with a browsable reference at /docs/. Generate your client from it if you'd rather not hand-write one. A few operations are marked Preview below: they're published and stable in shape, but return 501 not_implemented until they're switched on.

Support

Include the X-Request-Id of the failing call, or the traceId from the problem document, and we can find it immediately. Never include an API key in a support message — if you think one has been exposed, have the customer revoke it in Metis first and tell us second.