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

# Get an inbound network firm

> Returns the source firm's profile together with its relationship to the caller (firm → caller). When no relationship exists, `relationship` is null and `generalReferralInstructions` is included. Requires a firm-scoped API key.



## OpenAPI

````yaml /api-reference/openapi.yml get /network/inbound/{firmId}
openapi: 3.0.3
info:
  title: CaseXchange Public API
  version: 1.0.0
  description: >-
    The CaseXchange Public API enables law firms to integrate referral
    management

    directly into their own systems.


    ## Authentication

    All requests must include an `X-API-Key` header containing a valid API key:
    `cxp_<hex>` (firm-scoped) or

    `cxpp_<hex>` (provider-scoped). Keys are scoped to an owner — a firm or a
    Med Xchange provider — and an

    access tier. Salesforce-issued tokens (`cxsf_<hex>`) authenticate requests
    as firm context but are not a

    named, revocable public API key; a handful of actions (e.g. managing webhook
    subscriptions) require an

    actual `cxp_`/`cxpp_` key and reject an `sf_token` context with `403
    wrong_scope`.


    ## Access Tiers

    | Tier | Description |

    |------|-------------|

    | `read_only` | GET endpoints — read data without mutation |

    | `standard` | read_only + create/update case referrals |

    | `full` | standard + delete, routing rules, bulk import, firm mutation |


    ## Rate Limiting

    Rate limits are enforced per API key. Exceeded limits return `429 Too Many
    Requests`.


    ## Pagination

    List endpoints accept `page` (default 1) and `limit` (default 20, max 100)
    query params.


    ## Response Format

    Successful responses are wrapped in `{ data: T, meta: { requestId, timestamp
    } }`.

    Errors are wrapped in `{ error: { code, message }, meta: { requestId,
    timestamp } }`.


    ## Webhooks


    CaseXchange can push referral lifecycle events to an HTTPS endpoint you
    control, so you do not have to poll.

    Webhooks are notifications that something changed — not a synchronisation
    feed. Treat them as a signal to

    reconcile against `GET /status-updates`, `GET /referrals` and the other read
    endpoints, which remain the

    authority. There is no backfill: a subscription only ever receives events
    that happen after it is created.


    ### Subscribing


    Create a subscription with `POST /webhooks` using a firm-scoped `cxp_` key
    at `standard` tier or above; the

    GET endpoints need only `read_only`. Admin keys, Salesforce `cxsf_` tokens
    and MCP-issued keys cannot own a

    subscription and are rejected with `403 wrong_scope`, whatever their tier.
    Provider-scoped `cxpp_` keys manage

    their own Med Xchange subscriptions through the same endpoints, but those
    deliveries use a different body

    shape — everything below describes firm subscriptions.


    - A firm may hold **5 active** subscriptions. Creating a sixth, or
    re-activating one when five are already
      active, returns `400 subscription_limit_reached`.
    - The URL must be `https://` **on port 443**, carry no `user:password@`
    credentials, and resolve to a public IP
      address (a hostname that will not resolve, or that resolves to a private or reserved range, is rejected with
      `400 invalid_webhook_url`). Deliveries do not follow redirects: a 3xx counts as a failed attempt.
    - **Every active subscription receives every event for your firm.** There is
    no per-subscription event filter
      yet, so branch on the `event` field (or the `X-CaseXchange-Event` header) and ignore what you do not handle.
      Unknown event names should be ignored rather than treated as errors — new ones are added over time.
    - The signing `secret` is returned exactly twice: by `POST /webhooks` and by
    `POST /webhooks/{id}/rotate-secret`.
      It cannot be read back.

    ### What a delivery looks like


    Each delivery is a single HTTPS `POST` of a JSON body, with a 10-second
    timeout and no redirect following.


    | Header | Meaning |

    |--------|---------|

    | `X-CaseXchange-Event` | The event name — same value as `event` in the body
    |

    | `X-CaseXchange-Event-Id` | Identifies the *fact*. Shared by the deliveries
    to both firms and to all of your subscriptions |

    | `X-CaseXchange-Delivery` | Identifies *this* delivery. Resent unchanged on
    every retry — use it as your idempotency key |

    | `X-CaseXchange-Signature` | `t=<unix-seconds>,v1=<hex>` — see below |


    The body is the `CasexWebhookEnvelope` schema: a self-describing envelope so
    a low-code receiver can route and

    deduplicate without reading headers. `data` varies by event; the
    `x-webhook-events` extension at the root of

    this document maps every event name to its `data` schema, the perspectives
    that can receive it, and whether it

    can arrive masked.


    ```json

    {
      "schemaVersion": 1,
      "event": "referral.accepted",
      "eventId": "3f9d2c1e-7b44-4a1c-9e0b-2c5f1a8d6e21",
      "occurredAt": "2026-09-03T14:12:09.401Z",
      "perspective": "sent",
      "piiMasked": false,
      "recipientFirmId": "a1b2c3d4-0000-4000-8000-000000000001",
      "referralId": "6f0b8c2a-1d3e-4f5a-8b7c-9d0e1f2a3b4c",
      "baseCaseId": "2a91c4d6-8e7f-4a1b-9c2d-3e4f5a6b7c8d",
      "referenceNumber": "ACME-0042-BLF",
      "referringFirm": { "id": "a1b2c3d4-0000-4000-8000-000000000001", "name": "Acme Injury Law" },
      "referentFirm":  { "id": "a1b2c3d4-0000-4000-8000-000000000002", "name": "Bay Legal Firm" },
      "isTestCase": false,
      "actor": { "firmId": "a1b2c3d4-0000-4000-8000-000000000002", "source": "manual" },
      "data": { "...": "see x-webhook-events" }
    }

    ```


    ### Verifying the signature


    The `v1` value is `HMAC-SHA256` of `${t}.${rawBody}`, keyed with your
    subscription secret and hex-encoded,

    where `t` is the timestamp from the same header and `rawBody` is the exact
    request body as bytes. Verify

    before parsing the JSON — re-serialising changes the bytes and the signature
    will not match. Compare with a

    constant-time function (`crypto.timingSafeEqual` in Node) and reject a `t`
    more than about 5 minutes old to

    blunt replays.


    ```javascript

    const crypto = require("crypto");


    // `rawBody` MUST be the exact bytes we sent — verify before any JSON.parse.

    function verify(rawBody, signatureHeader, secret) {
      const parts = Object.fromEntries(
        signatureHeader.split(",").map((p) => p.split("=", 2))
      );
      const t = Number(parts.t);
      if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;

      const expected = crypto
        .createHmac("sha256", secret)
        .update(`${t}.${rawBody}`)
        .digest("hex");

      const a = Buffer.from(expected, "hex");
      const b = Buffer.from(parts.v1 ?? "", "hex");
      return a.length === b.length && crypto.timingSafeEqual(a, b);
    }

    ```


    ### Retries, the circuit breaker, and what your endpoint should return


    Return a `2xx` as soon as you have durably accepted the body, and do the
    work asynchronously. **Any** non-2xx

    response, timeout or connection failure counts as a failed attempt. A
    delivery is attempted up to **5 attempts**

    in total: a failed attempt is retried after 30 s, 2 min, 10 min and 30 min
    (about 43 minutes end to end), and

    after the fifth it is marked `EXHAUSTED` and never retried. `GET
    /webhooks/{id}/deliveries` shows the attempt

    count, last status code and last error — never the payload.


    After **10 consecutive** exhausted deliveries the subscription trips a
    circuit breaker and is deactivated

    (`isActive: false`). It stays visible on `GET /webhooks/{id}`; re-enable it
    with `PATCH /webhooks/{id}`

    `{ "isActive": true }` once your endpoint is healthy, subject to the
    5-active cap.


    ### Rotating the secret


    `POST /webhooks/{id}/rotate-secret` returns a new secret and starts signing
    with it immediately, including

    retries of deliveries created before the rotation. There is no dual-signing
    window: switch your verifier over

    at the moment you rotate, or briefly accept either secret on your side.


    ### Trying it out


    `POST /webhooks/{id}/test` queues a synthetic `webhook.test` delivery. It is
    not a CaseX firm event and does

    not use the envelope: its body is `{ event, timestamp, data }`, and its
    `eventId` travels only in the header.


    Echo mode points a subscription at a CaseXchange-hosted endpoint instead of
    yours, so you can read back the

    exact headers, raw body and a signature check for what would have been
    delivered. Enable it with

    `POST /webhooks/{id}/echo/enable`, read `GET /webhooks/{id}/echo/captures`,
    and restore your URL with

    `POST /webhooks/{id}/echo/disable`. Captures keep the **full** body, are
    limited to the 50 most recent per

    subscription, and are discarded after 1 hour.


    The echo URL and its token come back from `echo/enable` only — that call is
    idempotent, so call it again if you

    need them a second time. Reads never disclose them: while echo mode is on,
    `GET /webhooks` and

    `GET /webhooks/{id}` keep reporting **your** endpoint in `url` (and repeat
    it in `originalUrl`) with

    `isEchoActive: true`. The token is a credential, not a label — the echo
    endpoint accepts unauthenticated posts

    on it, and anyone holding it could push the 50-capture cap over and evict
    the deliveries you were inspecting.


    ### Client information and the receiving firm


    A firm that is **sent** a referral sees a masked snapshot until it
    acknowledges receipt itself. While it is

    masked, every delivery to that firm carries `piiMasked: true` and
    `data.case` is the `MaskedCaseSnapshot`:

    only `id`, `baseCaseId`, `referenceNumber`, `status`, `closingStatus`,
    `referringFirmId`, `referentFirmId`,

    `caseType`, `jurisdiction`, `county`, `sentDate`, `isRetainerSigned`,
    `createdAt` and `updatedAt`. Client

    name, contact details, date of birth, incident date, case title,
    description, lead attorney, warm-transfer

    text, the money block and custom fields are **absent from the object**, not
    present-and-null, and `message`

    is forced to null.


    - Only an action by the receiving firm unmasks it — its own user
    acknowledging in the app, via the
      acknowledgment email, or through a bulk status update it uploads itself; its connected case-management
      system; or its own Salesforce org. A platform administrator forcing a status, a historical CSV import, or
      the sending firm acting on its behalf never does.
    - In the normal flow the first unmasked delivery is `referral.accepted`.
    Until then, only the status family
      (`referral.received`, `referral.accepted`, `referral.rejected`, `case.status_changed`) is delivered at all:
      `case.updated`, `note.*` and `document.*` are withheld entirely from an unacknowledged receiving firm.
    - A referral withdrawn or rejected before it was ever acknowledged never
    unmasks, and earlier deliveries
      cannot be retracted.
    - A receiving firm never receives `case.created`, `case.rated`, the tier
    fields, or the sending firm's
      `source`. `document.*` additionally stops for the counterparty once the referral is CLOSED, where
      `case.updated` and `note.*` keep flowing; all three stop at the other terminal statuses.
    - Echo-mode captures return the full delivered body to the subscription
    owner, so treat echo mode the same
      way you treat the payload itself.

    ### Multi-hop cases


    A case can be referred onward, so one `baseCaseId` may carry several
    referrals. Each firm only ever sees its

    own hop: `referralId` identifies that hop and `referringFirm`/`referentFirm`
    name its two parties. Fields that

    belong to a hop — fees, settlement figures, lead attorney, phase — are
    delivered only to the two firms on the

    hop that changed; other firms on the same case are told only about
    case-level changes, and only when something

    they can see actually changed.


    ### Ordering, duplicates, and your own writes


    - **Deduplicate on `X-CaseXchange-Delivery`.** It is stable across retries.
    `eventId` is *not* unique per
      delivery: it is shared by the same fact across firms and subscriptions, which is what makes it useful for
      correlation.
    - There is **no ordering guarantee**. Dispatch is concurrent and a retried
    delivery can trail a later one by
      the better part of an hour, so deliveries arrive out of order. Apply the delivery with the greatest
      `occurredAt`.
    - One transaction can commit two status rows (for example CLOSING then
    CLOSED), and those two deliveries carry
      a byte-identical `occurredAt`. There is deliberately no tiebreaker field: chain them with
      `data.previousStatus` → `data.status`.
    - CaseXchange does not distinguish a change made in its own UI from one made
    through this API, so your own
      writes come back to you as events. `actor.firmId` tells you which firm acted, not through which channel; to
      suppress your own echo, compare the delivered state against what you last sent.

    ### Known gaps


    These are deliberate omissions today, listed so you can plan around them
    rather than discover them:


    - Historical CSV import creates and updates cases silently — no events. A
    bulk status update by CSV does emit,
      one event per row.
    - Status changes written by the Clio Manage pull bypass the status pipeline
    and produce no event for either
      firm. Reconcile with `GET /status-updates`.
    - Status changes written by the DearLegal inbound webhook produce no event
    and do not count as the receiving
      firm's acknowledgement.
    - Notes written automatically alongside a status transition, a case creation
    or a CSV import are not published
      as `note.created`. A note supplied to `PATCH /sent-cases/{id}` is a real note and does produce one.
    - `note.deleted` does not exist: nothing in CaseXchange produces it, so it
    is not in the catalog.

    - Archiving a case or a referral stops **all** events for it, and there is
    no event announcing the archive;
      unarchiving resumes them. Revoking your firm's `public_api` integration or deactivating the firm has the
      same effect, while `GET /webhooks/{id}` still reports `isActive: true`.
    - Partner messages and document metadata edits produce no events.

    - There is no per-subscription event filter and no endpoint to replay a
    delivery.

    - Delivery payloads are retained on our side for 30 days once delivered (90
    if exhausted) and hard-deleted
      after 180; `GET /webhooks/{id}/deliveries` never returns them.
  contact:
    name: CaseXchange API Support
    url: https://casexchange.com
servers:
  - url: https://api.casexchange.com/api/public/v1
    description: Production Public API base path
  - url: /api/public/v1
    description: Relative base path (for proxied docs/local environments)
security: []
tags:
  - name: Account
    description: Identity of the calling API key and its firm
  - name: Referrals
    description: >-
      Referral records visible to the caller's firm as sender or receiver, and
      the lifecycle transitions on them
  - name: Received Referrals
    description: >-
      Receiver-centric view of referrals sent TO the caller's firm, addressable
      by UUID, reference number or CMS id
  - name: Sent Cases
    description: 'Sender-side case management: create, track, and refer/route cases you own'
  - name: Status Updates
    description: Status-history and partner-message entries across the caller's referrals
  - name: Messages
    description: Partner messaging on a referral
  - name: Cases
    description: >-
      Legacy referral endpoints keyed by referral UUID, served from either
      party's perspective. Prefer Referrals, Received Referrals and Sent Cases
      for new integrations.
  - name: Documents
    description: Document upload, download, and management
  - name: Reference Data
    description: Case types, jurisdictions, and counties
  - name: Firms
    description: Firm directory and profile management
  - name: Network
    description: >-
      Referral relationships between the caller's firm and other firms.
      `outbound` is a relationship the caller sends referrals through (caller →
      target firm); `inbound` is one where the caller receives (source firm →
      caller). Requires a firm-scoped API key.
  - name: Routing
    description: Referral routing rules
  - name: Analytics
    description: Dashboard and case analytics
  - name: Export
    description: >-
      XLSX/CSV downloads of analytics and case data. Responses are binary
      attachments rather than the JSON envelope. Export endpoints carry an
      additional limit of 10 requests per API key per sliding minute, on top of
      the tier rate limits.
  - name: Import
    description: Bulk case import/update via CSV
  - name: Notifications
    description: Notification preferences and email management
  - name: Users
    description: User management within the caller's firm
  - name: External Links
    description: >-
      Links between a CaseXchange referral and a record in an external
      case-management system. Supported systems: `cloudlex`, `morgan_morgan`,
      `litify`. Requires a firm-scoped API key.
  - name: Webhooks
    description: >-
      Outbound event subscriptions. Available to both firm-scoped (`cxp_*`) and
      provider-scoped (`cxpp_*`) API keys — a subscription belongs to whichever
      kind of key created it, and each key can only see and manage its own
      subscriptions (up to 5 active). Admin keys, Salesforce-issued tokens
      (`cxsf_*` — these authenticate as firm context but are not a named,
      revocable key) and MCP-issued keys cannot manage webhooks and receive `403
      wrong_scope`.


      Each delivery is an HTTPS POST of a JSON body with headers
      `X-CaseXchange-Event` (event name), `X-CaseXchange-Event-Id` (identifies
      the fact), `X-CaseXchange-Delivery` (identifies the delivery, stable
      across retries — use it as your idempotency key) and
      `X-CaseXchange-Signature` in the form `t=<unix-seconds>,v1=<hex>`, where
      `v1` is HMAC-SHA256(secret, `<t>.<rawBody>`). Failed deliveries are
      retried with back-off up to 5 attempts (10-second request timeout); after
      that the delivery is marked `EXHAUSTED`.


      Firm-owned subscriptions receive `WebhookEventType` events (CaseXchange
      referral lifecycle) and their body is the self-describing
      `CasexWebhookEnvelope`; the `x-webhook-events` extension at the root of
      this document catalogues every event with its `data` schema and PII rules,
      and the `## Webhooks` section of the introduction is the narrative guide.
      Provider-owned subscriptions receive `MedexWebhookEventType` events (Med
      Xchange case lifecycle) and their body is the bare entity payload, with no
      envelope. Both kinds may also receive the synthetic `webhook.test` event
      from the "send test event" action, whose body is `{ event, timestamp, data
      }`.
  - name: Med Cases
    description: Med Xchange cases and their nested referrals to medical providers
  - name: Med Appointments
    description: >-
      Appointments on a Med Xchange case. Providers read and write their own;
      firms read.
  - name: Med Billing
    description: >-
      Billing lines (visit fees, procedures, other charges) attached to an
      appointment.
  - name: MedEx Documents
    description: Med Xchange case document listing and download
  - name: SSO
    description: >-
      Partner-initiated Med Xchange single sign-on. Requires a provider-scoped
      API key (`cxpp_*`).
  - name: Salesforce
    description: Org-facing endpoints consumed by the CaseXchange managed package
  - name: Extension
    description: Telemetry reported by the CaseXchange browser extension
  - name: Health
    description: Unauthenticated liveness probe
paths:
  /network/inbound/{firmId}:
    get:
      tags:
        - Network
      summary: Get an inbound network firm
      description: >-
        Returns the source firm's profile together with its relationship to the
        caller (firm → caller). When no relationship exists, `relationship` is
        null and `generalReferralInstructions` is included. Requires a
        firm-scoped API key.
      operationId: getInboundNetworkFirm
      parameters:
        - in: path
          name: firmId
          required: true
          schema:
            type: string
            format: uuid
          description: UUID of the counter-party firm
      responses:
        '200':
          description: Firm detail with inbound relationship
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/NetworkFirmDetail'
                  meta:
                    type: object
                    properties:
                      requestId:
                        type: string
                        example: req_f2dbe1d3f6ad4a7bb5b4c9f2
                      timestamp:
                        type: string
                        format: date-time
                    required:
                      - requestId
                      - timestamp
                required:
                  - data
                  - meta
        '400':
          description: Request validation failed
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                      - code
                      - message
                  meta:
                    type: object
                    properties:
                      requestId:
                        type: string
                        example: req_f2dbe1d3f6ad4a7bb5b4c9f2
                      timestamp:
                        type: string
                        format: date-time
                    required:
                      - requestId
                      - timestamp
                required:
                  - error
                  - meta
        '401':
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                      - code
                      - message
                  meta:
                    type: object
                    properties:
                      requestId:
                        type: string
                        example: req_f2dbe1d3f6ad4a7bb5b4c9f2
                      timestamp:
                        type: string
                        format: date-time
                    required:
                      - requestId
                      - timestamp
                required:
                  - error
                  - meta
        '403':
          description: >-
            API key tier does not permit this action, or the key is not
            firm-scoped (`wrong_scope`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                      - code
                      - message
                  meta:
                    type: object
                    properties:
                      requestId:
                        type: string
                        example: req_f2dbe1d3f6ad4a7bb5b4c9f2
                      timestamp:
                        type: string
                        format: date-time
                    required:
                      - requestId
                      - timestamp
                required:
                  - error
                  - meta
        '404':
          description: Firm not found (`firm_not_found`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                      - code
                      - message
                  meta:
                    type: object
                    properties:
                      requestId:
                        type: string
                        example: req_f2dbe1d3f6ad4a7bb5b4c9f2
                      timestamp:
                        type: string
                        format: date-time
                    required:
                      - requestId
                      - timestamp
                required:
                  - error
                  - meta
      security:
        - apiKeyAuth: []
components:
  schemas:
    NetworkFirmDetail:
      type: object
      properties:
        firmId:
          type: string
          format: uuid
        firmName:
          type: string
        jurisdictions:
          type: array
          items:
            type: string
        caseTypes:
          type: array
          items:
            type: string
          description: Display names of the firm's active, MVP-supported specialties
        inNetwork:
          type: boolean
          description: True when a relationship exists in this direction
        generalReferralInstructions:
          type: string
          nullable: true
          description: >-
            The firm's general referral instructions. Only present when there is
            no relationship (`inNetwork: false`).
        relationship:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/NetworkRelationship'
          description: Null when no relationship exists in this direction
      required:
        - firmId
        - firmName
        - jurisdictions
        - caseTypes
        - inNetwork
        - relationship
    NetworkRelationship:
      type: object
      properties:
        description:
          type: string
          nullable: true
        sendingPercentage:
          type: number
          nullable: true
          description: Referral fee percentage as a whole percent (33.33 means 33.33%)
        daysForUnderEvaluation:
          type: integer
          nullable: true
        daysForInvestigating:
          type: integer
          nullable: true
        referralInstructions:
          type: string
          nullable: true
      required:
        - description
        - sendingPercentage
        - daysForUnderEvaluation
        - daysForInvestigating
        - referralInstructions
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: >-
        Public API key: `cxp_<hex>` (firm-scoped) or `cxpp_<hex>`
        (provider-scoped). Required on every request.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.