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

# Agent Skills

> Install Agent Skills that teach Claude and other AI agents how to use the 9squid MCP server safely and correctly.

[Agent Skills](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview) are instruction files an AI agent loads when a task matches them. The 9squid skills teach an agent how to drive the MCP server: which role to use, how to read upstream errors, and which calls need human sign-off.

They cover behavior the raw tool list does not make obvious:

* Loan-tape and document uploads take three steps. The server returns a pre-signed URL and the client `PUT`s the file itself.
* `trigger_selection_criteria_run` is asynchronous. It returns a job ID, not results.
* `update_webhook` replaces the event list. It does not append to it.
* `complete_bulk_loan_upload` can report per-deal failures inside a successful response.
* Calls that delete records or commit capital need explicit human confirmation.

<Note>
  Skills describe how to *use* the MCP server. They do not register it. [Connect an AI client](/mcp-server/connect-to-claude) first.
</Note>

***

## Available skills

| Skill | Covers |
| - | - |
| `9squid-mcp` | Core skill: registration, roles, auth, tool inventory, error handling, safety rules |
| `9squid-mcp-loan-pipeline` | Originator: tape upload, QC, selection criteria, pools, ALM, approvals |
| `9squid-mcp-investor-desk` | Investor: browse, diligence, IOI, subscribe, allocations, portfolio |
| `9squid-mcp-webhooks` | Webhook subscriptions, event catalogue, delivery debugging, replay |

Install `9squid-mcp` in every case. The workflow skills assume it is present. You can install any subset of the other three.

***

## Install

Each skill is a directory that holds one `SKILL.md` file. Copy the directories to the place your client loads skills from.

### Claude Code

<Tabs>
  <Tab title="Per project">
    Commit the skills alongside the code that uses the MCP server. Everyone who works in the repository gets them.

    ```bash theme={null}
    mkdir -p .claude/skills
    cp -R 9squid-mcp* .claude/skills/
    ```
  </Tab>

  <Tab title="Per user">
    Make the skills available in every project on your machine.

    ```bash theme={null}
    mkdir -p ~/.claude/skills
    cp -R 9squid-mcp* ~/.claude/skills/
    ```
  </Tab>
</Tabs>

Start a new session. Claude Code loads a skill automatically when your request matches its description. You can also invoke one by name, for example `/9squid-mcp-loan-pipeline`.

### Claude Desktop and claude.ai

Zip each skill directory, so the archive contains `9squid-mcp/SKILL.md` and so on. Then upload the archives in **Settings → Capabilities → Skills**.

### Other agents

Any client that supports the Agent Skills format can load these files. For an agent without skill support, paste the relevant `SKILL.md` content into its system prompt or project instructions.

### Create the files

To create the skill files, copy each `SKILL.md` below into a directory with the same name as the skill:

```text theme={null}
9squid-mcp/SKILL.md
9squid-mcp-loan-pipeline/SKILL.md
9squid-mcp-investor-desk/SKILL.md
9squid-mcp-webhooks/SKILL.md
```

***

## Skill files

<Accordion title="9squid-mcp/SKILL.md">
  Read first. Every workflow skill depends on it.

  ````markdown 9squid-mcp/SKILL.md theme={null}
  ---
  name: 9squid-mcp
  description: Work with the 9squid securitization platform through the 9squid Platform MCP server — registering the server, choosing the originator or investor role, discovering the available tools, reading errors, and the safety rules that apply to every tool call. Use whenever a task involves 9squid data (loans, deals, pools, portfolio, alerts, webhooks) through MCP, or whenever a 9squid MCP tool needs to be called or debugged.
  ---

  # Using the 9squid Platform MCP server

  The 9squid MCP server exposes the 9squid Platform API Gateway as **53 MCP
  tools**. It forwards the caller's Clerk API key to the backend unchanged and
  gates tool *visibility* by the role declared at registration time.

  This skill covers the invariants that apply to *every* tool call. For
  multi-step business workflows, see the companion skills:
  **9squid-mcp-loan-pipeline** (originator), **9squid-mcp-investor-desk**
  (investor), **9squid-mcp-webhooks**.

  > The same 53 operations are also available as a command-line tool. If the
  > environment has the `9squid` CLI instead of this MCP server, use the
  > **9squid-cli** skill family.

  ## Registration

  Transport is MCP **Streamable HTTP**. Two things must be supplied on every
  request: a bearer token and a role.

  ```json
  {
    "mcpServers": {
      "9squid-originator": {
        "type": "http",
        "url": "https://<mcp-host>/mcp/?role=originator",
        "headers": {
          "Authorization": "Bearer ${NINE_SQUID_API_KEY}",
          "x-9squid-role": "originator"
        }
      }
    }
  }
  ```

  Use `?role=investor` (and the matching header) for investor users. Register
  one server per role — a single connection cannot hold both.

  Claude Code equivalent:

  ```sh
  claude mcp add --transport http --scope local \
    --header "Authorization: Bearer <your-api-key>" \
    --header "x-9squid-role: originator" \
    9squid-originator "https://<mcp-host>/mcp/?role=originator"
  ```

  Notes that matter:

  - **`--scope local`** keeps a personal key out of a shared project `.mcp.json`.
    If a project-level config is needed, use `${ENV_VAR}` expansion — never a
    literal key.
  - The `x-9squid-role` header is a **fallback** for clients and proxies (MCP
    Inspector among them) that drop the query string. The query parameter is the
    primary mechanism; sending both is safe and recommended.
  - The hosted staging server is `https://mcp-stg.9squid.com`. For local
    development the host is `http://localhost:8013`.

  ## Authentication

  The credential is a **Clerk API key** — the same bearer token the 9squid
  backend uses. There is no separate MCP auth scheme.

  The MCP server only checks that *a* bearer token is present; the upstream
  Platform API decides whether it is valid. So a connected server with visible
  tools does **not** mean the key works — the first `get_platform_health` or
  list call is the real test.

  **Never** ask a human to paste a key into chat, and never write one into a
  file, a logged command, or a commit. It belongs in the MCP client's secret or
  config mechanism only.

  ## Roles are fail-closed

  | Registration | Tools visible |
  |---|---|
  | `role=originator` | 39 |
  | `role=investor` | 31 |
  | missing or invalid role | **0** |

  The union of both roles is the full 53 operations. Role gating is enforced at
  tool *listing* time, so a tool that is not visible cannot be called at all.

  **Zero tools visible is a registration problem, not a permissions problem.**
  Check that the URL contains exactly `?role=originator` or `?role=investor`, or
  that the `x-9squid-role` header is present. Do not look for a way around role
  gating — reconnect with the correct role instead.

  ## Tool groups

  **Originator only** — see **9squid-mcp-loan-pipeline**

  - *Loans*: `initiate_loan_upload`, `complete_loan_upload`,
    `reinitiate_loan_upload`, `list_loans`, `get_loan`, `approve_or_reject_deal`,
    `initiate_bulk_loan_upload`, `complete_bulk_loan_upload`, `get_bulk_job`,
    `get_bulk_job_status`, `regenerate_bulk_loan_upload_urls`
  - *Selection criteria*: `trigger_selection_criteria_run`,
    `get_sc_results_for_deal`, `list_sc_exceptions`, `get_sc_exception`
  - *Pools*: `list_pools`, `get_pool`, `get_pool_by_deal`, `get_pool_summary`,
    `get_pool_stratification_report`
  - *Analytics*: `run_alm_analysis`, `get_alm_analysis`

  **Investor only** — see **9squid-mcp-investor-desk**

  - *Deals*: `list_investor_deals`, `get_investor_deal`, `get_investor_deal_pool`,
    `get_investor_deal_tranches`, `get_investor_deal_documents`,
    `get_investor_comparable_deals`, `get_investor_originator_profile`
  - *Orders*: `submit_indication_of_interest`, `submit_subscription`,
    `list_subscriptions`, `get_deal_allocations`
  - *Portfolio*: `get_portfolio_summary`, `get_portfolio_positions`,
    `get_portfolio_reports`

  **Shared by both roles**

  - *Deal-room documents*: `get_document_upload_url`, `complete_document_upload`,
    `list_deal_documents`, `get_deal_document`
  - *Webhooks* — see **9squid-mcp-webhooks**: `create_webhook`, `list_webhooks`,
    `get_webhook`, `update_webhook`, `delete_webhook`, `test_webhook`,
    `replay_webhook_delivery`, `get_webhook_deliveries`,
    `get_global_webhook_deliveries`
  - *Alerts*: `get_alerts`, `get_alert`, `delete_alert`
  - *Health*: `get_platform_health`

  ## Reading errors

  Tool failures surface as HTTP status from the upstream gateway. Read the
  status before retrying:

  | Status | Meaning | What to do |
  |---|---|---|
  | 400 / 422 | validation | an argument is wrong or missing — fix and retry |
  | 401 | auth | the Clerk API key is missing, expired, or wrong for this environment |
  | 403 | authorization | the key is valid but not entitled to this resource |
  | 404 | not found | the ID does not exist **in this environment** — re-list to get a real one |
  | 429 | rate limited | back off, then retry once |
  | 5xx | upstream degraded | call `get_platform_health`, retry later, do not loop |

  Two distinctions worth making explicitly:

  - **401/403 means the MCP connection is fine and the credential is not.** The
    server connected and the tools listed; the backend rejected the key.
  - **404 very often means "right ID, wrong environment."** The MCP server
    points at whatever `NEST_API_URL` is configured — dev, staging, or prod.
    Confirm which one before concluding a record was deleted.

  Use `get_platform_health` to separate "my credential is broken" from "the
  platform is down" when calls start failing.

  ## Never invent identifiers

  Deal, pool, webhook, alert, document, and exception IDs must come from a
  previous tool's output — never from memory, a pattern, or an example in this
  document. List first, then act.

  The same rule covers file names, `file_key` values, pre-signed URLs, monetary
  amounts, statuses, and tranche preferences. If a value was not returned by a
  tool or stated by the human, do not supply it.

  Response envelopes vary by endpoint (`{data: [...]}`, `{result: {...}}`, or a
  bare object). Inspect the shape actually returned rather than assuming one.

  ## Confirm before state-changing calls

  These tools change state, and several are irreversible or commit money.
  Get explicit human instruction — naming the resource, and for money commands
  the amount — before calling any of them:

  - `approve_or_reject_deal` — **`status` (`APPROVED` or `REJECTED`) must be
    stated explicitly**; never infer an approval from QC or SC output
  - `submit_subscription` — commits capital
  - `submit_indication_of_interest` — non-binding, but visible to the originator
  - `complete_loan_upload`, `complete_bulk_loan_upload`,
    `complete_document_upload` — register files against a deal
  - `create_webhook`, `update_webhook`, `delete_webhook`,
    `replay_webhook_delivery`
  - `delete_alert`

  Pre-signed upload and download URLs are credentials in themselves — treat them
  as sensitive and do not echo them into shared output.

  ## Uploads are not single calls

  Loan tapes and deal-room documents both follow a **get URL → PUT the file →
  register** pattern. The MCP server never transfers file bytes; the client does
  the `PUT` to the pre-signed URL directly. See **9squid-mcp-loan-pipeline** for
  the loan-tape flow.

  ## Verifying a connection

  ```sh
  # Server liveness (no auth required):
  curl https://<mcp-host>/health          # → {"status":"ok"}

  # Tool listing for a role:
  npx @modelcontextprotocol/inspector "https://<mcp-host>/mcp/?role=originator"
  ```

  Expected: `originator` → 39 tools, `investor` → 31, no role → 0. Listing tools
  outside an HTTP request (for example calling `main.mcp.list_tools()` directly)
  also returns 0, because no role is present — that is correct behavior, not a
  bug.
  ````
</Accordion>

<Accordion title="9squid-mcp-loan-pipeline/SKILL.md">
  ````markdown 9squid-mcp-loan-pipeline/SKILL.md theme={null}
  ---
  name: 9squid-mcp-loan-pipeline
  description: Run the 9squid originator loan pipeline through the MCP server — upload loan tapes (single or bulk), track QC and deal status, run selection criteria and triage exceptions, inspect securitization pools and stratification, run ALM analysis, and approve or reject deals. Use for any originator-side 9squid task involving loans, loan tapes, deals, SC runs, pools, or ALM.
  ---

  # 9squid originator loan pipeline (MCP)

  Read **9squid-mcp** first for registration, roles, error handling, and the
  safety rules. Every tool here requires an **originator** registration
  (`?role=originator`); on an investor connection they are not visible at all.

  Pipeline shape:

  ```
  upload tape → QC → selection criteria → exceptions → pool → ALM → approve/reject
  ```

  ## 1. Upload a loan tape (three steps, not one)

  The MCP server does **not** transfer the file. The API hands back a pre-signed
  URL and the file is uploaded to it directly by the client.

  1. **`initiate_loan_upload(loan_type)`** → returns `deal_id`, `upload_url`,
     `file_name`, `expires_in`.
  2. **`PUT` the file to `upload_url`** — outside MCP. For example:
     `curl -sSf -X PUT --upload-file ./tape.csv "<upload_url>"`
  3. **`complete_loan_upload(deal_id, file_name)`** → registers the file; the
     deal moves to `IN_REVIEW`.

  Notes that matter:

  - **Use the `file_name` from step 1**, not the local filename. Step 3
    validates against what the gateway registered.
  - Only **CSV or XLSX** are accepted; step 3 rejects anything else.
  - The URL expires in **1 hour**. If step 2 or 3 fails after that, call
    `reinitiate_loan_upload(deal_id)` for a fresh URL rather than starting over —
    it reuses the same deal.
  - Calling `initiate_loan_upload` again with the same loan type **reuses an
    existing DRAFT deal** instead of creating a second one. That is expected.
  - `complete_loan_upload` is state-changing — confirm the deal and file with the
    human before calling it.

  ## 2. Bulk upload

  1. **`initiate_bulk_loan_upload(loan_types)`** → one `bulk_job_id` plus one
     upload URL per deal.
  2. `PUT` each file to its own URL.
  3. **`complete_bulk_loan_upload(bulk_job_id, loans)`** where `loans` is a list
     of `{"loan_id": "...", "path": "..."}` objects — one per uploaded file,
     using the loan ID and file path returned for that file in step 1.

  Tracking:

  - `get_bulk_job(job_id)` — the job record.
  - `get_bulk_job_status(job_id)` — per-deal statuses.
  - `regenerate_bulk_loan_upload_urls(job_id)` — fresh URLs when they expire
    mid-run.

  **`complete_bulk_loan_upload` returns per-deal results including errors — a
  successful call does not mean every deal succeeded.** Inspect each entry and
  report failures individually rather than summarizing the call as "done".

  ## 3. Inspect deals and QC

  - `list_loans(page, limit)` — paginated deal list.
  - `get_loan(deal_id)` — the full record: QC results and lifecycle state.

  `get_loan` is the single source of truth for where a deal sits. Read its QC
  results before running selection criteria or proposing an approval.

  ## 4. Selection criteria

  - `trigger_selection_criteria_run(deal_id)` → returns `sc_job_id`.
  - `get_sc_results_for_deal(deal_id)` — results across all runs, showing which
    loans passed, failed, or raised exceptions.
  - `list_sc_exceptions()` — exceptions across every run for the originator.
  - `get_sc_exception(exception_file_id)` — loan-level detail and exception
    history.

  **`trigger_selection_criteria_run` is asynchronous** — it returns a job id, not
  results. Poll `get_sc_results_for_deal` instead of assuming completion. Wait
  several seconds between attempts, give up after a bounded number of tries, and
  report the `sc_job_id` so the human can follow up.

  ## 5. Pools

  - `list_pools()` — all pools for the originator.
  - `get_pool(pool_id)` — full pool detail.
  - `get_pool_by_deal(deal_id)` — which pool a deal landed in.
  - `get_pool_summary(pool_id)` — aggregates: `total_balance`, `loan_count`,
    `wa_coupon`, `wa_maturity`, `wa_ltv`, `wa_fico`, `wa_dti`, `wa_seasoning`,
    min/max/avg balance, `delinquency_breakdown`, `geographic_top_5`,
    `vintage_distribution`.
  - `get_pool_stratification_report(pool_id)` — returns a **download URL** for
    the report file, not the report contents. Fetch it separately if the human
    wants the file, and treat the URL as sensitive.

  ## 6. ALM analysis

  - `run_alm_analysis(deal_id)` — recomputes and returns the full TCT Risk
    report: duration (Macaulay, modified, effective, spread), convexity, key rate
    durations, NII sensitivity, EVE analysis, gap analysis, VaR metrics, stress
    test results.
  - `get_alm_analysis(deal_id)` — retrieves the stored results.

  The report is large. Prefer `get_alm_analysis` when the human just wants to see
  existing numbers, and summarize rather than dumping the whole payload.

  ## 7. Approve or reject — a human decision

  `approve_or_reject_deal(deal_id, status, reason)`

  - `status` must be exactly `APPROVED` or `REJECTED`; anything else is rejected
    before the call reaches the gateway.
  - **Never call this without the human explicitly stating the decision.** Do not
    infer an approval from clean QC or SC output — surface the evidence and let
    them choose.
  - A rejection requires a reason. Use the human's wording, not a paraphrase.
  - The call is effectively irreversible from the pipeline's point of view.

  ## Triage recipe

  When asked "what needs attention?":

  1. `get_alerts(status="UNREAD")` — unread notifications, highest priority first.
  2. `list_sc_exceptions()` — unresolved selection-criteria exceptions.
  3. `list_loans()` — then filter on lifecycle state for deals stuck in
     `IN_REVIEW`.

  Report each item **with its ID**, so follow-up calls can use it. Push
  notification instead of polling is available — see **9squid-mcp-webhooks**
  (`loan.qc.failed`, `loan.qc.exception`, `pool.finalized`,
  `report.alm.ready`).
  ````
</Accordion>

<Accordion title="9squid-mcp-investor-desk/SKILL.md">
  ````markdown 9squid-mcp-investor-desk/SKILL.md theme={null}
  ---
  name: 9squid-mcp-investor-desk
  description: Run the 9squid investor workflow through the MCP server — browse securitization deals, do diligence on pool data, tranches, documents and comparables, check originator track records, submit indications of interest and subscriptions, and review allocations and portfolio positions. Use for any investor-side 9squid task involving deals, IOIs, subscriptions, allocations, or portfolio.
  ---

  # 9squid investor desk (MCP)

  Read **9squid-mcp** first for registration, roles, error handling, and the
  safety rules. Every tool here requires an **investor** registration
  (`?role=investor`); on an originator connection they are not visible at all.

  Flow:

  ```
  browse → diligence → IOI (soft) → subscribe (binding) → allocation → portfolio
  ```

  ## 1. Browse deals

  `list_investor_deals(page, limit, search, status)` — paginated deal summaries
  with metrics and a tranche overview. `search` matches on name; `status` filters
  the lifecycle state.

  Take deal IDs from this output. Never construct one.

  ## 2. Diligence

  - `get_investor_deal(deal_id)` — deal detail including tranche structure.
  - `get_investor_deal_pool(deal_id)` — anonymized loan-level pool data plus
    aggregate statistics.
  - `get_investor_deal_tranches(deal_id)` — tranches with pricing.
  - `get_investor_comparable_deals(deal_id)` — comparables for relative value.
  - `get_investor_deal_documents(deal_id)` — investor-visible documents with
    download URLs.
  - `get_investor_originator_profile(originator_id)` — profile and track record.

  For a diligence summary, pull `get_investor_deal`,
  `get_investor_deal_tranches`, and `get_investor_comparable_deals` and present
  them together — **pricing means little without the comparable set.**

  The pool data is anonymized by design. Do not present it as loan-level PII or
  attempt to re-identify borrowers from it.

  Deal-room documents are also reachable through the shared document tools:

  - `list_deal_documents(deal_id, document_type=..., status=...)` —
    `document_type` is one of `legal`, `internal`, `loan_tape`,
    `credit_union_export`; `status` is `active`, `archived`, or `all`.
  - `get_deal_document(deal_id, document_id)` — metadata plus a **download URL**.

  The document tools return URLs, not file contents. Fetch the file separately if
  the human wants it, and treat the URL as sensitive.

  ## 3. Indication of interest — soft, but still a signal

  `submit_indication_of_interest(deal_id, interest_amount, notes)`

  `interest_amount` is in **USD** (e.g. `500000` for $500k). An IOI is
  non-binding but **visible to the originator**. Only submit one with an amount
  the human has explicitly given.

  ## 4. Subscription — binding, confirm first

  `submit_subscription(deal_id, subscription_amount, tranche_preferences, notes)`

  This **commits capital** and triggers compliance checks. Rules:

  - Never call it without the human naming both the deal and the amount.
  - `subscription_amount` is in USD. **Confirm the figure back before
    executing** — a misplaced zero is a six-figure error.
  - `tranche_preferences` is an optional list of
    `{"tranche_id": "...", "amount": 500000}` objects. Use tranche IDs returned
    by `get_investor_deal_tranches`, never invented ones, and check that the
    preference amounts reconcile with `subscription_amount`.
  - The response carries `subscription_id`, a `status` (typically
    `pending_confirmation`), and `compliance_check_result`. **Read the compliance
    result** — a successful call does not mean the subscription cleared.

  ## 5. Track subscriptions and allocations

  - `list_subscriptions(page, limit, status, deal_id)` — across all deals.
  - `get_deal_allocations(deal_id, include_tranche)` — per-tranche
    `allocated_amount`, `pct_fill`, `settlement_date`, `confirmation_id`. Set
    `include_tranche=True` for full tranche details.

  Allocations are the final word on what was actually filled. **Expect them to
  differ from the subscribed amount** and report the fill percentage, not just
  the amount.

  ## 6. Portfolio

  - `get_portfolio_summary()` — total invested, NAV, returns, allocation
    breakdown.
  - `get_portfolio_positions()` — individual deal holdings and valuations.
  - `get_portfolio_reports()` — holdings snapshot, trade history, tranche
    performance, pool-level metrics.

  For a portfolio review, start with `get_portfolio_summary`, then pull
  `get_portfolio_positions` for detail. In `get_portfolio_reports`, performance
  sections are **omitted** when no calculation job has run yet — an absent
  section means "not calculated", not "zero".

  ## Watching for events

  - `get_alerts(status="UNREAD")` — allocation finalization, pricing changes, and
    document updates surface here. Filter further by `type`, `category`, or
    `priority`.
  - `get_alert(alert_id)` — a single alert.

  For push notifications instead of polling, see **9squid-mcp-webhooks**
  (`deal.priced`, `subscription.confirmed`, `allocation.finalized`).
  ````
</Accordion>

<Accordion title="9squid-mcp-webhooks/SKILL.md">
  ```markdown 9squid-mcp-webhooks/SKILL.md theme={null}
  ---
  name: 9squid-mcp-webhooks
  description: Manage and debug 9squid webhook subscriptions through the MCP server — register endpoints for platform events, send test pings, inspect the delivery log, diagnose failed deliveries, and replay them. Use whenever a 9squid task involves webhooks, event subscriptions, missed or failing deliveries, or integration debugging.
  ---

  # 9squid webhooks (MCP)

  Read **9squid-mcp** first for registration, roles, error handling, and the
  safety rules. Webhook tools are visible to **both** originator and investor
  registrations.

  ## Register a subscription

  `create_webhook(url, events, secret, description, active, headers)`

  - `url` and `events` are required; `events` is a list of event-type strings.
  - `secret` (**min 16 characters**) signs delivery payloads so the receiver can
    verify them. Always set one for a real endpoint. Take it from the human's
    secret store or an environment variable — **never generate one and never
    echo it back** into chat, logs, or a commit.
  - `headers` is an optional dict of custom headers sent with every delivery
    (e.g. `{"X-Tenant": "acme"}`).
  - `active` defaults to `true`.

  ## Event catalogue

  Subscribe narrowly to the events the integration actually handles rather than
  to everything:

  - **loans**: `loan.ingested`, `loan.qc.passed`, `loan.qc.failed`,
    `loan.qc.exception`, `loan.updated`, `loan.repurchase.demanded`
  - **pools**: `pool.created`, `pool.finalized`, `pool.stratification.complete`,
    `pool.constraint.violated`
  - **deals**: `deal.created`, `deal.structured`, `deal.priced`, `deal.closed`,
    `deal.amended`, `deal.called`
  - **orders**: `ioi.received`, `subscription.submitted`,
    `subscription.confirmed`, `allocation.finalized`
  - **reporting**: `report.generated`, `report.alm.ready`,
    `report.exception.flagged`, `remittance.received`, `performance.updated`,
    `delinquency.threshold.breached`, `modification.reported`
  - **partners**: `partner.loan.forwarded`, `partner.loan.accepted`,
    `partner.loan.rejected`, `partner.loan.securitized`
  - **documents**: `document.uploaded`, `document.signed`, `document.expired`
  - **account**: `user.created`, `user.deactivated`, `api_key.rotated`,
    `api_key.revoked`

  The tool's own schema is the authoritative list. An event name not in it is
  rejected at validation time.

  ## Inspect and update

  - `list_webhooks(page, limit, is_active)` — all subscriptions, optionally
    filtered by active status.
  - `get_webhook(webhook_id, include_deliveries)` — one subscription, optionally
    with its delivery history.
  - `update_webhook(webhook_id, url, events, active, headers)` — a **PATCH**:
    only the fields passed are changed.

  **Passing `events` replaces the whole list rather than adding to it.** Read the
  current events with `get_webhook` first and send the full intended set.

  To pause a subscription without losing it, call
  `update_webhook(webhook_id, active=False)`.

  ## Test an endpoint

  `test_webhook(webhook_id, event_type)` sends a test ping. Use it right after
  `create_webhook` to prove the endpoint is reachable, instead of waiting on real
  traffic to find out.

  ## Debug deliveries

  - `get_webhook_deliveries(webhook_id, ...)` — delivery history for one
    subscription.
  - `get_global_webhook_deliveries(...)` — the log across all subscriptions;
    accepts an optional `webhook_id` filter.

  Both accept `status` (`pending`, `succeeded`, `failed`, `retrying`),
  `event_type`, `date_from` / `date_to` (**ISO 8601 UTC** strings), `page`, and
  `limit`.

  ## Replay a failed delivery

  `replay_webhook_delivery(webhook_id, delivery_id)` re-sends the original
  payload. Take `delivery_id` from a deliveries listing — never construct it.

  **If the receiver is not idempotent, a replay double-processes the event.**
  Confirm with the human before replaying, and especially before replaying in
  bulk.

  ## Diagnostic sequence

  When someone reports "we're not getting events":

  1. `list_webhooks()` — does the subscription exist, and is it active?
  2. `get_webhook(id)` — is the event actually in its `events` list, and is the
     URL right?
  3. `get_webhook_deliveries(id, status="failed")` — was it attempted and
     rejected?
  4. `test_webhook(id, event_type)` — is the endpoint reachable *now*?
  5. Once the receiver is fixed, replay the failed deliveries.

  State which of the two failure modes it is:

  - **No delivery attempts** → a subscription or configuration problem: wrong
    events, inactive subscription, wrong URL.
  - **Failed attempts** → the receiver rejected them; the status codes and error
    detail are in the delivery log.

  ## Deleting

  `delete_webhook(webhook_id)` is **permanent and takes the delivery history with
  it.** Confirm with the human first, and when they only want to pause a webhook,
  set `active=False` instead.

  ## Alerts as an alternative

  When an integration endpoint is not available, the same lifecycle events are
  visible by polling `get_alerts(status="UNREAD")`. Webhooks are the push path;
  alerts are the pull path.
  ```
</Accordion>

***

## Command-line equivalent

The same 53 operations ship as the [`9squid` CLI](/cli), which has a parallel skill set: `9squid-cli`, `9squid-cli-loan-pipeline`, `9squid-cli-investor-desk`, and `9squid-cli-webhooks`. The two families use distinct names, so you can install both for an agent that has access to both.


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