> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signsealship.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Closing Rooms API: create, attach orders, and rotate links

> Create Verified Closing Rooms, attach orders by public code, rotate bearer links, and read the live room view with progress, evidence badges, and activity.

A Verified Closing Room is one shareable page per transaction that bundles the deal's orders — documents, signers, notarization, shipping — with live status and verifiable evidence links. You create the room with your partner key, attach the orders whose public codes you hold, and hand every party the same link. Statuses are read live from the order records on every view, never replayed from events.

<Warning>
  **The room link is a bearer credential.** One link grants deal-wide
  visibility: every attached document's status, masked signer progress,
  evidence badges, tracking, and drill-down into each order page. Share it
  with the parties to the deal and no one else; never post or embed it
  anywhere public. If a link leaks, [rotate it](#rotate-the-room-link) — the
  old link stops working immediately.
</Warning>

All partner routes below require your key (`Authorization: Bearer sss_pk_...`) and share the `partner-write` limit of 60 requests per minute per key. The public room view is rate limited at 30 requests per minute per IP. See [authentication](/api-reference/authentication).

## Create a room

`POST /api/rooms`

Room creation enforces your tier's active-room quota (trial and PartnerLink: 5 open rooms; ProOffice: 25; ClosingDesk: unlimited).

<ParamField body="name" type="string" required>
  Display name for the room, 1–200 characters. Shown to every party who opens
  the link — a property address or matter name works well.
</ParamField>

<ParamField body="reference" type="string">
  Your internal file number, at most 200 characters. Displayed on the room
  page.
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://signsealship.com/api/rooms \
    -H "Authorization: Bearer sss_pk_your_key" \
    -H "Content-Type: application/json" \
    -d '{ "name": "1428 Maple St — Refinance", "reference": "TC-88412" }'
  ```

  ```javascript Node theme={null}
  const res = await fetch("https://signsealship.com/api/rooms", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SSS_PARTNER_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "1428 Maple St — Refinance",
      reference: "TC-88412",
    }),
  });
  const room = await res.json();
  console.log(`https://signsealship.com${room.roomUrl}`);
  ```

  ```python Python theme={null}
  import os
  import requests

  res = requests.post(
      "https://signsealship.com/api/rooms",
      headers={"Authorization": f"Bearer {os.environ['SSS_PARTNER_KEY']}"},
      json={"name": "1428 Maple St — Refinance", "reference": "TC-88412"},
  )
  room = res.json()
  print(f"https://signsealship.com{room['roomUrl']}")
  ```
</CodeGroup>

<ResponseField name="roomCode" type="string">
  The room's bearer code — 40 lowercase hex characters, minted from a CSPRNG.
  This is the credential in the room link.
</ResponseField>

<ResponseField name="roomUrl" type="string">
  Root-relative path to the shareable page, `/rooms/{roomCode}`. Prefix it
  with `https://signsealship.com` before sharing.
</ResponseField>

<ResponseField name="name" type="string">
  The room name, as stored.
</ResponseField>

<ResponseField name="reference" type="string">
  Your reference, or `null`.
</ResponseField>

<ResponseField name="status" type="string">
  `"open"` on creation.
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 creation time.
</ResponseField>

```json 200 OK theme={null}
{
  "roomCode": "3f1c9a7e5b2d8c4a6e0f9b1d3a5c7e9f2b4d6a8c",
  "roomUrl": "/rooms/3f1c9a7e5b2d8c4a6e0f9b1d3a5c7e9f2b4d6a8c",
  "name": "1428 Maple St — Refinance",
  "reference": "TC-88412",
  "status": "open",
  "createdAt": "2026-07-12T18:04:11+00:00"
}
```

Errors: `400` with `{"error": "A room name (1–200 chars) is required."}` for a missing or oversized name, or a quota message such as `"Active-room quota reached (5 open rooms on the None tier). Close a room or upgrade your plan to create more."`

## List your rooms

`GET /api/rooms`

Returns your own rooms only, newest first.

<CodeGroup>
  ```bash curl theme={null}
  curl https://signsealship.com/api/rooms \
    -H "Authorization: Bearer sss_pk_your_key"
  ```

  ```javascript Node theme={null}
  const res = await fetch("https://signsealship.com/api/rooms", {
    headers: { Authorization: `Bearer ${process.env.SSS_PARTNER_KEY}` },
  });
  const { rooms } = await res.json();
  ```

  ```python Python theme={null}
  import os
  import requests

  rooms = requests.get(
      "https://signsealship.com/api/rooms",
      headers={"Authorization": f"Bearer {os.environ['SSS_PARTNER_KEY']}"},
  ).json()["rooms"]
  ```
</CodeGroup>

<ResponseField name="rooms" type="array">
  One summary per room.

  <Expandable title="room summary">
    <ResponseField name="roomCode" type="string">
      The room's current bearer code.
    </ResponseField>

    <ResponseField name="name" type="string">
      Room name.
    </ResponseField>

    <ResponseField name="reference" type="string">
      Your reference, or `null`.
    </ResponseField>

    <ResponseField name="status" type="string">
      Room status, `"open"` by default.
    </ResponseField>

    <ResponseField name="orderCount" type="integer">
      Number of attached orders.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      ISO 8601 creation time.
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      ISO 8601 time of the last attach, detach, or rotation.
    </ResponseField>
  </Expandable>
</ResponseField>

## Attach an order

`POST /api/rooms/{roomCode}/orders`

Attach authorization is possession of the order's public code: a partner who holds the code may bind that order to their room. Attaching is idempotent — re-attaching an already attached order succeeds with `alreadyAttached: true`. A room holds at most 50 orders.

<ParamField path="roomCode" type="string" required>
  Your room's code.
</ParamField>

<ParamField body="orderCode" type="string" required>
  The order's public code, 24–64 characters.
</ParamField>

<ParamField body="label" type="string">
  A display label for the document card (for example "Seller deed package").
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://signsealship.com/api/rooms/{roomCode}/orders \
    -H "Authorization: Bearer sss_pk_your_key" \
    -H "Content-Type: application/json" \
    -d '{ "orderCode": "the-orders-public-code", "label": "Seller deed package" }'
  ```

  ```javascript Node theme={null}
  const res = await fetch(
    `https://signsealship.com/api/rooms/${roomCode}/orders`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SSS_PARTNER_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        orderCode: "the-orders-public-code",
        label: "Seller deed package",
      }),
    },
  );
  ```

  ```python Python theme={null}
  import os
  import requests

  res = requests.post(
      f"https://signsealship.com/api/rooms/{room_code}/orders",
      headers={"Authorization": f"Bearer {os.environ['SSS_PARTNER_KEY']}"},
      json={"orderCode": "the-orders-public-code", "label": "Seller deed package"},
  )
  ```
</CodeGroup>

<ResponseField name="ok" type="boolean">
  `true` on success.
</ResponseField>

<ResponseField name="alreadyAttached" type="boolean">
  `true` when the order was already in the room (the call is a no-op).
</ResponseField>

Errors: `400` `{"error": "A valid orderCode is required."}` (bad length), `400` `{"error": "A room holds at most 50 orders."}`, `404` `{"error": "Room not found."}` or `{"error": "Order not found."}` — unknown codes read as a generic not-found.

## Detach an order

`DELETE /api/rooms/{roomCode}/orders/{orderCode}`

<ParamField path="roomCode" type="string" required>
  Your room's code.
</ParamField>

<ParamField path="orderCode" type="string" required>
  The attached order's public code.
</ParamField>

```bash curl theme={null}
curl -X DELETE https://signsealship.com/api/rooms/{roomCode}/orders/{orderCode} \
  -H "Authorization: Bearer sss_pk_your_key"
```

Returns `{"ok": true}`, or `404` with `{"error": "Room not found."}`, `{"error": "Order not found."}`, or `{"error": "That order is not in this room."}`.

## Rotate the room link

`POST /api/rooms/{roomCode}/rotate`

Reissues the room's bearer code. Every previously shared room link dies instantly — this is the mitigation for a leaked or over-shared link. The public view is served with `Cache-Control: no-store`, so a rotated code dies at every cache layer too.

<ParamField path="roomCode" type="string" required>
  The room's current code. It stops working the moment this call returns.
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://signsealship.com/api/rooms/{roomCode}/rotate \
    -H "Authorization: Bearer sss_pk_your_key"
  ```

  ```javascript Node theme={null}
  const res = await fetch(
    `https://signsealship.com/api/rooms/${roomCode}/rotate`,
    {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.SSS_PARTNER_KEY}` },
    },
  );
  const { roomCode: newCode, roomUrl } = await res.json();
  ```

  ```python Python theme={null}
  import os
  import requests

  res = requests.post(
      f"https://signsealship.com/api/rooms/{room_code}/rotate",
      headers={"Authorization": f"Bearer {os.environ['SSS_PARTNER_KEY']}"},
  )
  new_code = res.json()["roomCode"]
  ```
</CodeGroup>

<ResponseField name="roomCode" type="string">
  The new bearer code. Update every place you shared the old link.
</ResponseField>

<ResponseField name="roomUrl" type="string">
  Root-relative path with the new code, `/rooms/{roomCode}`.
</ResponseField>

Returns `404` `{"error": "Room not found."}` for a code that is not one of your rooms.

<Note>
  Per-party scoped links (separate credentials per participant) are on the
  roadmap; today one room has one link.
</Note>

## Read the room (public)

`GET /api/rooms/{roomCode}`

Public read by possession of the room code — no API key, no account. This is the same live JSON the room page at `/rooms/{roomCode}` renders, for your own systems. Codes outside the 24–64 character gate and unknown codes both return an empty `404`. Responses are `Cache-Control: no-store`.

Statuses are database truth read at request time. Evidence badges never claim more than the record holds.

<ParamField path="roomCode" type="string" required>
  The room's bearer code, 24–64 characters.
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl https://signsealship.com/api/rooms/{roomCode}
  ```

  ```javascript Node theme={null}
  const res = await fetch(`https://signsealship.com/api/rooms/${roomCode}`);
  if (res.ok) {
    const room = await res.json();
    console.log(room.progress); // { total, signed, sealed, delivered }
  }
  ```

  ```python Python theme={null}
  import requests

  res = requests.get(f"https://signsealship.com/api/rooms/{room_code}")
  if res.ok:
      room = res.json()
      print(room["progress"])
  ```
</CodeGroup>

### Response shape

<ResponseField name="roomCode" type="string">
  The room's bearer code.
</ResponseField>

<ResponseField name="name" type="string">
  Room name.
</ResponseField>

<ResponseField name="reference" type="string">
  The partner's file reference, or `null`.
</ResponseField>

<ResponseField name="status" type="string">
  Room status, `"open"` by default.
</ResponseField>

<ResponseField name="partnerName" type="string">
  The name of the partner firm that owns the room.
</ResponseField>

<ResponseField name="branding" type="object">
  The partner's public branding, or `null` when none is set. Rendered as
  attribution ("Presented by ...") beside the permanent SignSealShip mark —
  never replacing it.

  <Expandable title="branding">
    <ResponseField name="brandName" type="string">
      Display name, or `null`.
    </ResponseField>

    <ResponseField name="accentColor" type="string">
      Hex color like `#1a2b3c`, or `null`.
    </ResponseField>

    <ResponseField name="logoUrl" type="string">
      Public https URL of the partner's logo, or `null`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="progress" type="object">
  The progress strip, counted from the order cards below.

  <Expandable title="progress">
    <ResponseField name="total" type="integer">
      Number of attached orders in the view.
    </ResponseField>

    <ResponseField name="signed" type="integer">
      Orders where every signer has signed (orders with no signers are not
      counted).
    </ResponseField>

    <ResponseField name="sealed" type="integer">
      Orders whose evidence badge is `sealed`.
    </ResponseField>

    <ResponseField name="delivered" type="integer">
      Orders in the `delivered` or `complete` status group.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="orders" type="array">
  One card per attached order, in attach order.

  <Expandable title="order card">
    <ResponseField name="orderCode" type="string">
      The order's public code — the same code that opens
      `/orders/{orderCode}`.
    </ResponseField>

    <ResponseField name="label" type="string">
      The label you set at attach time, or `null`.
    </ResponseField>

    <ResponseField name="status" type="string">
      The order's raw platform status (for example `"EsignPending"`,
      `"NotarizationComplete"`, `"Delivered"`). This vocabulary can grow —
      build logic on `statusGroup` instead.
    </ResponseField>

    <ResponseField name="statusGroup" type="string">
      Stable coarse grouping: `signing`, `signed`, `notarizing`, `notarized`,
      `shipping`, `shipped`, `delivered`, `complete`, `attention`, or `open`.
      Statuses the room does not understand map to `open`, never to a complete
      state.
    </ResponseField>

    <ResponseField name="services" type="object">
      Which services the order includes: `sign`, `notarize`, and `ship`
      booleans.
    </ResponseField>

    <ResponseField name="signers" type="array">
      Signer progress. Emails arrive pre-masked — the room never exposes a
      full signer email.

      <Expandable title="signer">
        <ResponseField name="email" type="string">
          Masked email address.
        </ResponseField>

        <ResponseField name="name" type="string">
          Signer name, or `null`.
        </ResponseField>

        <ResponseField name="routingOrder" type="integer">
          Position in the signing order.
        </ResponseField>

        <ResponseField name="signed" type="boolean">
          Whether this signer has signed.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="evidenceBadge" type="string">
      Locked vocabulary. `sealed` — a cryptographically verifiable artifact
      hash is on record. `recorded` — platform attestation only; a milestone
      happened but no independent artifact exists. `none` — no evidence yet,
      stated explicitly.
    </ResponseField>

    <ResponseField name="verifyCode" type="string">
      Present only when the badge is `sealed` and the envelope's verify code
      exists — it links to public seal verification at `/v/{verifyCode}`. A
      sealed envelope whose verify code has not been generated yet returns
      `sealed` with `verifyCode: null`; verification is then available through
      the drop portal at `/verify`.
    </ResponseField>

    <ResponseField name="trackingNumber" type="string">
      Latest shipment tracking number when the order ships, else `null`.
    </ResponseField>

    <ResponseField name="carrier" type="string">
      Carrier for that tracking number, else `null`.
    </ResponseField>

    <ResponseField name="attachedAt" type="string">
      ISO 8601 time the order was attached to the room.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="activity" type="array">
  Merged timeline across all attached orders — up to 100 items, newest first,
  derived from an explicit allowlist of audit actions: `order.created`,
  `order.transition`, `order.claimed`, `order.cancelled`,
  `signing_link.issued`, `room.order_attached`. No recipient identifiers or
  actor details are ever included.

  <Expandable title="activity item">
    <ResponseField name="orderCode" type="string">
      Which order the event belongs to.
    </ResponseField>

    <ResponseField name="action" type="string">
      One of the allowlisted action names.
    </ResponseField>

    <ResponseField name="fromStatus" type="string">
      Previous status for transitions, or `null`.
    </ResponseField>

    <ResponseField name="toStatus" type="string">
      New status for transitions, or `null`.
    </ResponseField>

    <ResponseField name="occurredAt" type="string">
      ISO 8601 event time.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="passport" type="object">
  The room's latest Closing Passport, or `null` until one is sealed. This is
  only a pointer — verification always goes through the
  [public verify surface](/api-reference/passports), which recomputes the
  chain.

  <Expandable title="passport">
    <ResponseField name="version" type="integer">
      Latest version number.
    </ResponseField>

    <ResponseField name="verifyCode" type="string">
      Public verification code for `/v/room/{verifyCode}`.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      When that version was sealed.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 room creation time.
</ResponseField>

<ResponseField name="updatedAt" type="string">
  ISO 8601 time of the last room change.
</ResponseField>

```json 200 OK (abridged) theme={null}
{
  "roomCode": "3f1c9a7e5b2d8c4a6e0f9b1d3a5c7e9f2b4d6a8c",
  "name": "1428 Maple St — Refinance",
  "reference": "TC-88412",
  "status": "open",
  "partnerName": "Maple Title Co.",
  "branding": {
    "brandName": "Maple Title",
    "accentColor": "#16285a",
    "logoUrl": "https://storage.googleapis.com/.../branding/.../logo.png"
  },
  "progress": { "total": 2, "signed": 1, "sealed": 1, "delivered": 0 },
  "orders": [
    {
      "orderCode": "an-order-public-code",
      "label": "Seller deed package",
      "status": "NotarizationComplete",
      "statusGroup": "notarized",
      "services": { "sign": true, "notarize": true, "ship": true },
      "signers": [
        { "email": "d***@m***.com", "name": "Dana Reyes", "routingOrder": 1, "signed": true }
      ],
      "evidenceBadge": "sealed",
      "verifyCode": "abcdefghijklmnopqrstuvwxyz",
      "trackingNumber": null,
      "carrier": null,
      "attachedAt": "2026-07-12T18:10:02+00:00"
    }
  ],
  "activity": [
    {
      "orderCode": "an-order-public-code",
      "action": "order.transition",
      "fromStatus": "NotarizationPending",
      "toStatus": "NotarizationComplete",
      "occurredAt": "2026-07-12T19:22:40+00:00"
    }
  ],
  "passport": {
    "version": 1,
    "verifyCode": "zyxwvutsrqponmlkjihgfedcba",
    "createdAt": "2026-07-12T20:01:00+00:00"
  },
  "createdAt": "2026-07-12T18:04:11+00:00",
  "updatedAt": "2026-07-12T19:22:40+00:00"
}
```
