8am MyCase MCP Server — Tool Catalog
This catalog documents the 130 tools the MyCase MCP Server can expose — the same name, description, parameter, and annotation data an MCP client receives for them from tools/list — organized by domain. Tools badged beta are enabled per firm, so a connection that is not enrolled receives fewer tools than are listed here.
- Access is user-scoped: every call authenticates as an individual MyCase user via OAuth and can only see or change what that user can in MyCase.
- Read tools work for every connected firm. Write tools are badged beta: they are off by default and appear in Claude only for firms enrolled in the write beta — see Write tools (beta) below.
- No delete tools are exposed — deletions must be done in the MyCase app.
- The destructive badge does not mean a tool deletes anything. It marks a write that cannot be taken back: an update_ tool overwrites the fields you send and MyCase keeps no earlier version of the record, create_message_thread and reply_to_message_thread deliver a message to a person and it cannot be un-sent, and convert_lead cannot be un-converted.
- Invoices, payments and trust accounts are read-only; expenses and time entries have write tools.
- report_feedback files a bug report or feature request about these tools with the MyCase team; it is badged write because it has an effect outside MyCase, but it reads and changes no firm data, and it is not part of the write beta.
Legal Terms
- This is experimental. The MCP is currently a Beta Service, provided "as is" and "as available." It may contain defects or produce errors, may change without notice, and may be suspended or discontinued at any time.
- Your data goes to Claude. Once you connect, Claude — a Third-Party Service operated by Anthropic — can retrieve data across your firm's MyCase account. This may include privileged attorney-client communications, attorney work product, and other confidential or sensitive client information.
- Write actions may not be reversible or fully visible. Writes will appear in your firm's Recent Activities log, tagged to the connector and the acting user, but that log is a summary and doesn't show what the record said before. These records have no version history or undo, so an approved overwrite can't be restored.
- Data leaves MyCase. Information accessed through the connector is processed in your Claude instance under your firm's agreement with Anthropic, not your agreement with 8am. 8am doesn't control, and isn't responsible for, how Anthropic stores, uses, retains, or secures it.
- Your Claude plan matters. You must hold a Claude Team or Enterprise subscription that excludes firm data from model training by default. On consumer plans, inputs may be used to train AI models, which may defeat attorney-client confidentiality and privilege.
- Access may be ongoing and automated. Depending on how you configure Claude, the connector may run scheduled or automated tasks that access your MyCase data on a recurring basis after your initial authorization, without approval for each run.
- AI output may be inaccurate. Claude may produce incomplete, inaccurate, or misleading output about case details, deadlines, and financial figures. You are responsible for verifying all output before relying on it in a client matter.
- Restricted uses. You may not use the connector to make, or materially influence, decisions about employment, credit, finance, housing, insurance, health care, education, or public benefits.
- You control access. Your firm decides who may connect and when to disconnect. Disconnecting stops future access but doesn't delete information already shared with Claude.
- You assume the risk. Your use of the connector is at your own risk. To the maximum extent permitted by law, 8am disclaims all liability for any harm arising out of or in connection with the connector.
Write tools (beta)
Some of the tools below change MyCase data: they create a record, add a record to a case, or update fields on a record. Each one is badged beta. Write tools are in a limited beta and are off by default, so connecting Claude to MyCase does not turn them on.
A firm has to be enrolled before its connection can use them. Until your firm is enrolled the write tools are not in the tool list Claude receives, so Claude does not show them and does not offer to use them. Asked for one by name, it comes back with a message saying your firm is not enrolled. Nothing is broken and there is nothing to fix. Read tools work the same either way.
Enrollment is per firm, not per user: it covers every user who connects, still limited to what that user can do in MyCase. To ask about enrolling your firm, email MyCase Support at support@mycase.com.
Before you enroll, know these limits:
- Writes cannot be undone from Claude. A write takes effect in MyCase as soon as the tool runs, and the connector exposes no delete tools, so a record created by mistake has to be removed in the MyCase app.
- Claude asks you to approve a write before it runs, but that prompt comes from Claude, not from MyCase. Automations, scheduled tasks and Claude's own "always allow" setting can skip it. Do not rely on being asked for anything that needs an attorney's judgment.
- convert_lead cannot be undone. It converts the lead into a client and opens its potential case as a real case, approving a lead still pending review as part of the conversion. MyCase has no un-convert.
- Some update tools replace a list rather than add to it. On update_case, update_client, update_company, update_document, update_event and update_task, sending a list of people or records replaces that whole list: anyone left out is unassigned, with no error and no warning. Use the matching add tool where one exists — there is none for case contacts or document staff, so those can only be changed by replacing the list.
- Addresses behave two ways. The street, city, state, ZIP and country fields must be sent together or the call is rejected. A second address line left out of an otherwise complete address is cleared without an error.
- Adding a document takes two steps, and the first one already creates the record. create_document, create_case_document and create_document_version register the document and return a URL to upload the file to. If the upload never happens, the record stays in MyCase with no file attached, and the connector has no tool to remove it.
Connect Claude to MyCase
MyCase isn't listed in Claude's connector directory, so add it as a custom connector. The install links below open Claude's own dialog with the connector name and URL already filled in; the URL is spelled out further down if you'd rather enter it yourself. One connector covers everywhere you use Claude — the web app, Claude Desktop, and Cowork — because connectors are configured on your Claude account rather than per device.
Install link for your firm
If you administer your firm's Claude organization, this link prefills the connector for everyone in it. Claude flags that the values arrived from an external link and adds nothing until you review them and confirm.
As a Claude organization owner, open the prefilled install link for a Claude organization.
Install link for one person
If your Claude plan lets you add your own connectors, this link prefills the same dialog for your account alone. Where connectors are administered centrally, an organization owner adds the connector once and it then appears in your own connector settings for you to turn on.
For your own Claude account, open the prefilled install link for a single Claude account.
Connector URL
The links above only prefill Claude's dialog. If one doesn't open it, add this URL as a custom connector by hand instead:
https://mcp.mycase.com/mcp
You sign in with your own MyCase account, and every tool call runs as your MyCase user, held to that user's permissions.
For the steps in Claude, see Anthropic's guide to adding a custom connector.
What Claude Can Access
Connecting the connector grants Claude access to the following, for the individual MyCase user who authorizes it and only what that user can already see or do in MyCase.
Billing
get_case_billing_summary
Get Case Billing Summary read-only
Get a case's billing snapshot: fee_structure, totals (billed/collected/outstanding/overdue), balances (trust/credit funds held), aging, bills_by_status, unbilled work in progress, most_recent_payment, and most_recent_bill. Sections the user may not view are OMITTED, not zeroed: totals, balances, aging, bills_by_status, and most_recent_* require permissions.view_bills; unbilled is omitted only when the user has neither view_bills nor view_time_expenses (or the token lacks read_expenses). The permissions block is always present — use it to tell 'no access' from 'no billing on this case'. fee_structure (billing_type, agreed flat_fee, billing_contact) is the only source of the case's fee arrangement; unbilled.flat_fees is the flat-fee amount REMAINING to bill, distinct from the agreed fee_structure.flat_fee (itemized flat-fee line items come from list_case_billing_flat_fees). totals.outstanding counts invoiced balances only (draft invoices included), so flat-fee money not yet invoiced is in unbilled.flat_fees, not there. balances are client funds held, separate from the bill-side totals — a client can owe outstanding and still hold trust/credit; report both, never net them. credit is a per-contact, firm-wide snapshot with no provenance surface — do not sum a contact's credit across their cases, and do not claim where it came from. totals, aging, and bills_by_status exclude balance-forwarded bills, which list_case_invoices includes (status 'forwarded') — counts may legitimately differ. These billing tools do not surface retainer requests, payment plans or installment schedules, saved payment methods, failed or declined payment attempts, or write-off/discount line-item detail beyond get_case_invoice_line_items — direct the user to the MyCase app for those.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes |
get_case_invoice_line_items
Get Case Invoice Line Items read-only
Get the line items that compose one invoice, grouped by kind — time_entries, expenses, flat_fees, discounts — plus balances_forwarded (prior invoices whose unpaid balance rolled into this one) and the invoice header. Reconciliation: total_amount = billable time_entries' total + billable expenses' total + billable flat_fees' amount + all SIGNED discount amounts + balances_forwarded amounts, clamped at zero; nonbillable items appear with billable false and contribute nothing, and some firms fold payment surcharges into total_amount that are not itemized here. discounts rows are not all reductions: type discount/write_off is stored NEGATIVE, interest/tax/addition POSITIVE — sum amounts as-is, never flip signs by label. flat_fees are the BILLED flat-fee line items, distinct from the agreed fee_structure.flat_fee and from summary unbilled.flat_fees (remaining to bill). Visibility is the invoice's: a user without billing permission gets not_found (the invoice is invisible), so cross-check get_case_billing_summary's permissions.view_bills before concluding the invoice does not exist; unlike the case billing time/expense feeds, no read_expenses scope is needed because items are read as parts of the invoice document. Not paginated — one response carries the invoice's full contents.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
invoice_id | integer | yes |
list_case_billing_expenses
List Case Billing Expenses read-only
List a case's expenses. Each expense carries a server-computed total; the response has no list-level total, so to total the case sum each expense's total across every page. A non-billable expense still carries a nonzero total, so leave billable=false rows out of a billable total. filter_billed reflects actual invoice association (the same definition the billing summary uses); billed=false does NOT imply billable — also check the billable field when reconciling against summary.unbilled. Requires the read_expenses token scope (same as list_all_expenses) plus the time-entries-and-expenses OR billing user permission, and returns 403 without them — given the scope, a billing-permission-only user can use this tool even where list_all_expenses is denied.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_updated_after | string | no | Only records updated strictly after this ISO8601 timestamp (exclusive bound, unlike the inclusive entry-date filters) |
filter_entry_date_after | string | no | Only expenses dated on or after this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_entry_date_before | string | no | Only expenses dated on or before this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_billed | boolean | no | true → only expenses already on an invoice; false → only expenses not yet invoiced |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_case_billing_flat_fees
List Case Billing Flat Fees read-only
List a case's flat-fee billing line items: fixed-amount charges with no activity, rate, or duration — the third line-item type alongside time entries and expenses. Flat fees never appear in the time-entry feed, and BILLED flat fees appear in get_case_invoice_line_items (its flat_fees group); but UNBILLED flat fees appear only here, so a work-in-progress total built without this tool misses unbilled flat-fee revenue. They are also distinct from flat-RATE time entries, which list_case_billing_time_entries does include. amount is the charge (total is the same value, for symmetry with the sibling feeds). filter_billed reflects actual invoice association, and billed=false does NOT imply billable — also check the billable field. Do not reconcile this feed against summary.unbilled.flat_fees: that rollup is the agreed fee_structure.flat_fee minus billed billable items, not a sum of the unbilled rows here. Requires the read_expenses token scope plus the time-entries-and-expenses OR billing user permission, and returns 403 without them — given the scope, a billing-permission-only user can use this tool even where list_all_time_entries is denied.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_updated_after | string | no | Only records updated strictly after this ISO8601 timestamp (exclusive bound, unlike the inclusive entry-date filters) |
filter_entry_date_after | string | no | Only flat fees dated on or after this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_entry_date_before | string | no | Only flat fees dated on or before this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_billed | boolean | no | true → only flat fees already on an invoice; false → only flat fees not yet invoiced |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_case_billing_payments
List Case Billing Payments read-only
List successful payments applied to a case's bills. Bill payments only: retainer, subscription, and quick-bill receipts are excluded, and so are pending/failed transactions — never infer 'no failed charges' from this feed. For any of those (all payable types, all statuses) scoped to this same case, use list_all_payments with filter_case_id. amount is SIGNED: refunds/voids are negative with is_refund true, so summing the list gives the correct net — do not count refunds as income. client on each payment is the bill's billing contact, never the staff member who recorded it. Requires the caller's billing permission and returns 403 without it, so an empty list means the case genuinely has no matching payments.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_updated_after | string | no | Only records updated strictly after this ISO8601 timestamp (exclusive bound) |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_case_billing_time_entries
List Case Billing Time Entries read-only
List a case's time entries. Each entry carries a server-computed total that prices flat-rate entries correctly — prefer it over hours x rate math. The response has no list-level total: to total the case, sum each entry's total across every page. A non-billable entry still carries a nonzero total, so leave billable=false rows out of a billable total. Flat-FEE billing line items are separate records that NEVER appear in any time-entry feed — include list_case_billing_flat_fees when totaling case billing (flat-RATE time entries, by contrast, are ordinary rows here). filter_billed reflects actual invoice association (the same definition the billing summary uses); billed=false does NOT imply billable — also check the billable field when reconciling against summary.unbilled. Requires the read_expenses token scope (same as list_all_time_entries) plus the time-entries-and-expenses OR billing user permission, and returns 403 without them — given the scope, a billing-permission-only user can use this tool even where list_all_time_entries is denied.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_updated_after | string | no | Only records updated strictly after this ISO8601 timestamp (exclusive bound, unlike the inclusive entry-date filters) |
filter_entry_date_after | string | no | Only entries dated on or after this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_entry_date_before | string | no | Only entries dated on or before this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_billed | boolean | no | true → only entries already on an invoice; false → only entries not yet invoiced |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_case_invoices
List Case Invoices read-only
List a case's invoices (bills), scoped to the case server-side. On a case the caller can see, list_all_invoices with filter_case_id at its default only_allowed_online_payments=false returns the same rows for the same caller; on a case they cannot see, this tool 404s while that one applies no case check at all and returns whatever bills on it are shared with them. Each invoice carries balance_due, and its due_date is the bill's actual next payment due date (payment-plan aware, matching the UI's overdue math). Includes balance-forwarded bills (status 'forwarded'), which the billing summary's totals exclude — counts may legitimately differ. Requires the caller's billing permission and returns 403 without it, so an empty list is never a permission artifact — but it is still not proof the case has no invoices: a caller who cannot view all firm cases sees only the bills shared with them, and the billing summary's bill-side sections are narrowed by that same rule, so neither tool can reveal the rest. Send the user to the MyCase app when a bill they expect is missing.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_updated_after | string | no | Only records updated strictly after this ISO8601 timestamp (exclusive bound) |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
Calls
create_call
Create Call write beta
Create a new call log entry. Must provide exactly one of: client_id, lead_id, or caller_name.
| Parameter | Type | Required | Description |
|---|---|---|---|
called_at | string | yes | Call time (ISO8601) |
caller_phone_number | string | yes | |
call_for_id | integer | yes | Staff member ID the call was for |
message | string | yes | Call message/notes |
client_id | integer | no | Client ID (mutually exclusive with lead_id and caller_name) |
lead_id | integer | no | Lead ID (mutually exclusive with client_id and caller_name) |
caller_name | string | no | Caller name (mutually exclusive with client_id and lead_id) |
case_id | integer | no | Case ID to associate the call with (independent of the caller) |
call_type | string | no | Call direction (defaults to incoming). One of: incoming, outgoing. |
resolved | boolean | no | Whether the call has been resolved/answered (defaults to false) |
get_call
Get Call read-only
Get a specific call log entry by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
call_id | integer | yes |
list_callers
List Callers read-only
List all callers (clients and leads that can appear in call logs).
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_calls
List Calls read-only
List all call log entries, most recent call first.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_call
Update Call write destructive beta
Update a call log entry.
| Parameter | Type | Required | Description |
|---|---|---|---|
call_id | integer | yes | |
called_at | string | no | |
caller_phone_number | string | no | |
call_for_id | integer | no | |
message | string | no | |
client_id | integer | no | |
lead_id | integer | no | |
caller_name | string | no | |
case_id | integer | no | |
call_type | string | no | One of: incoming, outgoing. |
resolved | boolean | no |
Case Stages
create_case_stage
Create Case Stage write beta
Create a new case stage. Name must be unique within the firm.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Case stage name (must be unique) |
get_case_stage
Get Case Stage read-only
Get a specific case stage by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_stage_id | integer | yes |
list_case_stages
List Case Stages read-only
List all case stages.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
search_starts_with | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_case_stage
Update Case Stage write destructive beta
Update a case stage name.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_stage_id | integer | yes | |
name | string | no |
Cases
add_clients_to_case
Add Clients To Case write beta
Add clients to a case. Additive — existing case members are preserved (unlike update_case, whose list params replace).
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
client_ids | array[integer] | yes | Client IDs to add |
role | string | no | The party's role on this case (e.g. Adverse Party, Judge), applied to every ID in this call, including IDs already on the case, whose current role it replaces; make one call per role to give different roles. It must be an existing role name from list_case_roles (matched case-insensitively). An unknown name is refused and nothing is added, since MyCase roles are firm-wide and created only in MyCase settings. Omit to add without a role. |
add_companies_to_case
Add Companies To Case write beta
Add companies to a case. Additive — existing case members are preserved (unlike update_case, whose list params replace).
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
company_ids | array[integer] | yes | Company IDs to add |
role | string | no | The party's role on this case (e.g. Adverse Party, Judge), applied to every ID in this call, including IDs already on the case, whose current role it replaces; make one call per role to give different roles. It must be an existing role name from list_case_roles (matched case-insensitively). An unknown name is refused and nothing is added, since MyCase roles are firm-wide and created only in MyCase settings. Omit to add without a role. |
add_staff_to_case
Add Staff To Case write beta
Add staff members to a case, optionally making one of them the lead or originating lawyer. Additive — existing case members are preserved (unlike update_case, whose list params replace).
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
staff_ids | array[integer] | yes | Staff IDs to add. Staff already on the case may be included; they stay on it once. |
lead_lawyer_id | integer | no | Staff ID to make the case's lead lawyer, taking the role from whoever holds it. Must also be one of the staff_ids in this call, or the call is refused. If the new holder is already on the case, MyCase can refuse the move with a 'can only be set for one user per case' error. Do not remove the current holder or drop them from staff_ids to get around it; tell the user to reassign the role in MyCase. |
originating_lawyer_id | integer | no | Staff ID to record as the case's originating lawyer, taking the role from whoever holds it. Must also be one of the staff_ids in this call, or the call is refused. If the new holder is already on the case, MyCase can refuse the move with a 'can only be set for one user per case' error. Do not remove the current holder or drop them from staff_ids to get around it; tell the user to reassign the role in MyCase. |
create_case
Create Case write beta
Create a new case in MyCase.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Case name |
case_number | string | no | |
description | string | no | |
status | string | no | One of: open, closed. |
case_stage_id | integer | no | Case stage ID |
case_stage_name | string | no | Case stage by name (mutually exclusive with case_stage_id) |
practice_area_id | integer | no | Practice area ID |
practice_area_name | string | no | Practice area by name (mutually exclusive with practice_area_id) |
office_id | integer | no | Office ID to assign the case to. Find valid IDs in get_firm's offices list. If omitted, MyCase assigns the firm's primary office when one is configured. Assigning an office matters: a case with no office is hidden from MyCase's default case view. |
client_ids | array[integer] | no | Client IDs to associate |
staff_ids | array[integer] | no | Staff IDs to assign. If omitted, MyCase adds the connected user to the case's staff (not as lead or originating lawyer) so the case shows in their My Cases. A list you pass is used exactly as given, and an empty list leaves the case with no staff. |
lead_lawyer_id | integer | no | Staff ID to make the case's lead lawyer. Must also be one of the staff_ids in this call, or the call is refused. |
originating_lawyer_id | integer | no | Staff ID to record as the case's originating lawyer. Must also be one of the staff_ids in this call, or the call is refused. |
contact_ids | array[integer] | no | Contact IDs to associate |
company_ids | array[integer] | no | Company IDs to associate |
billing_contact_id | integer | no | |
outstanding_balance | number | no | Amount the client already owes on this case at the moment it is created, e.g. 2500.00 — MyCase stores it as the case's agreed flat fee and reads it back as outstanding_balance on get_case and list_cases. Setting it also sets the case's billing type to 'mixed' (hourly work plus a flat amount), so omit it to leave the firm's default billing configuration in place, and pass 0 only if you actually want a mixed-billing case with a zero balance. A negative value is rejected. Not available on update_case: it cannot be corrected through these tools once the case exists. |
opened_date | string | no | Date case was opened (ISO8601) |
closed_date | string | no | |
sol_date | string | no | Statute of limitations date |
custom_field_values | string | no | Values for the custom fields that apply to a case, set on the new case. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Acme"},{"custom_field":{"id":43},"value":"2026-01-05"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields with filter_parent_type='case'; a field defined for another record type is rejected. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. Case custom fields are also scoped by practice area, which list_custom_fields does not report: a value for a field that is not enabled for the practice area the case has after this call is rejected with a validation error naming that field. Drop that field and retry, and tell the user that field is not enabled for the case's practice area. A case with no practice area accepts every case field. |
create_case_document
Create Case Document write beta
Create a new document record in a case's folder and get a presigned upload URL — step 1 of a two-step upload. This call sends metadata only (no file content). The response includes the new document's id plus top-level put_url and put_headers fields. Step 2: upload the file content yourself with an HTTP PUT of the raw bytes to put_url, sending every header in put_headers exactly as returned; if put_headers does not include a Content-Type, also send 'Content-Type: application/octet-stream' (the content type the URL is signed for). The document is visible via get_document or list_case_documents once created.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
path | string | no | Folder path within the case's Documents folder, e.g. 'Pleadings' or 'Discovery/Depositions'; missing folders are created. Omit to file the document at the case root. |
filename | string | yes | Document filename |
description | string | no | |
assigned_date | string | no | The document's own date, as an ISO8601 date, e.g. '2026-01-05'. Despite the name this is the document's datestamp — the date the document itself carries — and NOT a date on which the document was assigned to anyone. Only ISO8601 parses: any other format, including the US-style 01/05/2026, is rejected with a 422, 'assigned_date is invalid'. This field is date-only: a full ISO8601 timestamp is accepted rather than rejected, but its time and offset are silently discarded instead of converted, so a UTC-normalized datetime can land on the wrong calendar day — send the bare date. |
create_case_note
Create Case Note write beta
Create a note on a case.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
note | string | yes | Note content |
date | string | yes | Note date (ISO8601 date). MyCase requires it and has no default; for a note written now, pass today's date in the user's own time zone. |
subject | string | no |
create_case_status_update
Create Case Status Update write beta
Post a new Status update on a case: a short free-text write-up of where the matter stands, shown as the newest entry in the case's Status updates panel with the connected user as author. Firm-internal: clients never see them. Requires permission to edit the case. Like posting in MyCase, it adds a firm notification. Use list_case_status_updates to read the history, and create_case_note for a note with its own subject and date.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
description | string | yes | The status update text, 500 characters at most. Leading and trailing whitespace is trimmed. |
create_case_subfolder
Create Case Subfolder write beta
Create a subfolder in a case's document folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
path | string | yes | Relative path for the new subfolder |
get_case
Get Case read-only
Get a specific case by ID. Includes the full staff roster (per-staff lead_lawyer, originating_lawyer, and rate details) and latest_status_update — the newest free-text status write-up firm staff keep on the matter, or null if there is none. Status updates are firm-internal: clients never see them, so do not quote one into client-facing text. Use list_case_status_updates for the full history. outstanding_balance is the case's full flat-fee amount (the same value as get_case_billing_summary's fee_structure.flat_fee): the agreed flat fee, or a balance entered when the case was created or imported. It does not go down as that fee is invoiced or paid, so do not report it as the current balance. For what is owed on a case, use get_case_billing_summary: unbilled.flat_fees is the part of the flat fee not yet invoiced, and totals.outstanding is the unpaid balance on the case's invoices. The Outstanding Balance Total on a MyCase client or company page adds both up across that contact's open cases, plus unpaid retainer requests, and leaves out draft invoices, which totals.outstanding includes (list_case_invoices shows each invoice's status and balance_due). A case that came from a converted lead keeps the lead's referral_source (name), referral_source_reference ({id}) and referred_by ({id} of the referring client or company; the id does not say which, so try get_client, then get_company). Each is null when the case did not come from a lead or the lead had no value for it; all three are omitted when the user cannot view leads.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
field_client | array[string] | no | Client fields to add to every nested client reference on the case — the contacts and clients entries, and billing_contact when the case is billed to a client. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. Use it to read client details straight off the case instead of calling get_client once per client id. Cost: each field is serialized once per reference, so twice for every client on the case plus once more for its billing contact. Request only the fields you will use; id is always present. If your MyCase user cannot view clients, these names are not added and each nested client stays {id} only; the case fields themselves are unchanged. One of: id, first_name, last_name, middle_initial, middle_name, email, contact_group, birthdate, cell_phone_number, work_phone_number, home_phone_number, fax_phone_number, updated_at, created_at. |
get_case_folder
Get Case Folder read-only
Get the root document folder for a case.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes |
list_case_documents
List Case Documents read-only
List documents for a case.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_case_notes
List Case Notes read-only
List notes for a case. Case notes are a distinct record type from Status updates, with their own subject, date and archived state — use list_case_status_updates for the running status write-ups. For notes on a lead's potential case, use list_lead_notes.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_status | string | no | One of: active, archived. |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_case_roles
List Case Roles read-only
List all case roles.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_case_status_updates
List Case Status Updates read-only
List a case's Status update history, newest first. These are the short free-text write-ups firm staff keep on a matter — outreach attempts, investigator contact details, next steps. Each carries its author and timestamps; updated_by is null until an update is edited. Firm-internal: clients never see them. get_case returns only the newest one. Not an audit trail: any staffer who can edit the case can overwrite an entry in place, and deleted entries leave no trace.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
page_size | integer | no | Items per page (default 25, max 100) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_cases
List Cases read-only
List all cases. Supports filtering by status, updated_after, and text search. Each case carries a staff summary (staff_count and lead_lawyer) instead of the full staff roster, and office_reference ({id}) instead of the full office; use get_case for full staff assignments and office details, or get_firm for the firm's offices. outstanding_balance is the case's full flat-fee amount (the same value as get_case_billing_summary's fee_structure.flat_fee): the agreed flat fee, or a balance entered when the case was created or imported. It does not go down as that fee is invoiced or paid, so do not report it as the current balance. For what is owed on a case, use get_case_billing_summary: unbilled.flat_fees is the part of the flat fee not yet invoiced, and totals.outstanding is the unpaid balance on the case's invoices. The Outstanding Balance Total on a MyCase client or company page adds both up across that contact's open cases, plus unpaid retainer requests, and leaves out draft invoices, which totals.outstanding includes (list_case_invoices shows each invoice's status and balance_due). A case that came from a converted lead keeps the lead's referral_source (name), referral_source_reference ({id}) and referred_by ({id} of the referring client or company; the id does not say which, so try get_client, then get_company). Each is null when the case did not come from a lead or the lead had no value for it; all three are omitted when the user cannot view leads.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_status | string | no | Filter by case status. One of: open, closed. |
filter_updated_after | string | no | ISO8601 datetime |
search_starts_with | string | no | Text search on case name |
field_client | array[string] | no | Client fields to add to every nested client reference on each case — the contacts and clients entries, and billing_contact when the case is billed to a client. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. Use it to read client details straight off a page of cases instead of calling get_client once per client id. Cost: each field is serialized once per reference, so twice for every client on the case plus once more for its billing contact — on a full page of cases, asking for six fields makes a very large response. Request only the fields you will use; id is always present. If your MyCase user cannot view clients, these names are not added and each nested client stays {id} only; the case fields themselves are unchanged. One of: id, first_name, last_name, middle_initial, middle_name, email, contact_group, birthdate, cell_phone_number, work_phone_number, home_phone_number, fax_phone_number, updated_at, created_at. |
field_custom_field | array[string] | no | Custom-field attributes to add to the nested custom_field reference inside each custom_field_values entry. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. field_type is the only value MyCase accepts here, so this widens {id} to {id, field_type} and no further — for a custom field's name, parent type, or list options, call get_custom_field or list_custom_fields. One of: field_type. |
page_size | integer | no | Items per page (default 25, max 200) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_case
Update Case write destructive beta
Update an existing case. Each list param (client_ids, staff_ids, contact_ids, company_ids) REPLACES the case's full set of that association — any existing member not included in the list is removed from the case; omit a list param to leave that association unchanged. To add members without removing existing ones, use add_clients_to_case, add_staff_to_case, or add_companies_to_case. lead_lawyer_id and originating_lawyer_id work only alongside staff_ids, so setting either one also replaces the staff roster. custom_field_values is not one of these replace-in-full params: it is an upsert keyed on custom field id, so a field you include overwrites only that field, and a field you omit keeps its current value.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
name | string | no | |
case_number | string | no | |
description | string | no | |
status | string | no | One of: open, closed. |
case_stage_id | integer | no | |
case_stage_name | string | no | |
practice_area_id | integer | no | |
practice_area_name | string | no | |
office_id | integer | no | Office ID to assign the case to. Find office IDs in get_firm's offices list. If omitted, the case's current office is left unchanged; unlike create_case, update does not assign the firm's primary office. |
client_ids | array[integer] | no | |
staff_ids | array[integer] | no | |
lead_lawyer_id | integer | no | Staff ID to make the case's lead lawyer, taking the role from whoever holds it. Must also be one of the staff_ids in this call, or the call is refused. If the new holder is already on the case, MyCase can refuse the move with a 'can only be set for one user per case' error. Do not remove the current holder or drop them from staff_ids to get around it; tell the user to reassign the role in MyCase. Because staff_ids replaces the whole roster, list everyone who should stay on the case. To change only the role, use add_staff_to_case with just the new holder in staff_ids. Omit to leave the role unchanged. |
originating_lawyer_id | integer | no | Staff ID to record as the case's originating lawyer, taking the role from whoever holds it. Must also be one of the staff_ids in this call, or the call is refused. If the new holder is already on the case, MyCase can refuse the move with a 'can only be set for one user per case' error. Do not remove the current holder or drop them from staff_ids to get around it; tell the user to reassign the role in MyCase. Because staff_ids replaces the whole roster, list everyone who should stay on the case. To change only the role, use add_staff_to_case with just the new holder in staff_ids. Omit to leave the role unchanged. |
contact_ids | array[integer] | no | |
company_ids | array[integer] | no | |
billing_contact_id | integer | no | |
opened_date | string | no | |
closed_date | string | no | |
sol_date | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to this case. This is an UPSERT keyed on custom field id, not a replacement of the case's whole set: a field you include OVERWRITES its current value, and a field you OMIT is LEFT UNCHANGED. Omitting a field does not clear it, so send only the fields you are changing — do not re-send a field's existing value to preserve it, and omit the param entirely when you are changing no custom fields. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Acme"},{"custom_field":{"id":43},"value":"2026-01-05"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields with filter_parent_type='case'; a field defined for another record type is rejected. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. Case custom fields are also scoped by practice area, which list_custom_fields does not report: a value for a field that is not enabled for the practice area the case has after this call is rejected with a validation error naming that field. Drop that field and retry, and tell the user that field is not enabled for the case's practice area. A case with no practice area accepts every case field. |
Chats
list_case_chat_conversations
List Case Chat Conversations read-only
List the firm's internal staff chat conversations linked to a case. This is staff-to-staff chat inside the firm — NOT client messaging: secure client-portal threads are a separate surface, read with list_message_threads / list_thread_messages. Returns conversation metadata only (id, name, description, conversation_type, private_conversation, created_by, created_at, updated_at, last_message_at); message bodies are NOT included — pass a conversation's id to list_chat_conversation_messages to read them. Use last_message_at to pick the active channel BEFORE fetching messages, rather than calling the messages tool once per conversation. Read it precisely: it is the newest updated_at across the messages the companion tool serves (text and announcement, not reactions), so EDITING an old message advances it. It is last activity, NOT when the last thing was said — do not report it to the user as the most recent message's send time. Two limits worth stating to the user rather than reading past: only conversations the CALLER is an active participant in are returned, so an empty list means 'none you are in', not 'none on this case'; and only case-linked channels are reachable at all, so firm-wide channels and direct messages between staff are invisible to this tool no matter who asks. Archived conversations are excluded. Participant lists are not exposed, so this cannot answer who is in a channel.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_updated_after | string | no | Matches conversations whose metadata OR whose messages changed strictly after this ISO8601 timestamp (exclusive bound), so a new message surfaces its conversation; a timestamp with no UTC offset is read as UTC. Only readable messages count as message activity: an emoji reaction does not surface its conversation here, matching the text-and-announcement messages list_chat_conversation_messages serves for the same filter. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_chat_conversation_messages
List Chat Conversation Messages read-only
Read the messages in one internal staff chat conversation in insertion order — ascending id, the order rows were stored — which is NOT a guaranteed chronological sort. The two agree for live chat and diverge for backdated or imported history, so when chronology matters, sort on the created_at each message already carries. Get the conversation's id from list_case_chat_conversations. Returns what people actually said — posted messages and system announcements such as someone joining a channel; emoji reactions are excluded, matching what the MyCase chat thread itself shows. Each message carries its author, timestamp, and reply_to_message_id for threaded replies. Returns 404 if the conversation does not exist on that case OR the caller is not an active participant in it — the two are deliberately indistinguishable, so do not report a 404 as proof the conversation does not exist. Busy channels run long; page with page_size and page_token rather than assuming one call is the whole history.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
chat_conversation_id | integer | yes | |
filter_updated_after | string | no | Only messages whose own updated_at is strictly after this ISO8601 timestamp (exclusive bound); a timestamp with no UTC offset is read as UTC, and a non-ISO8601 value is rejected upstream. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
Clients
create_client
Create Client write beta
Create a new client in MyCase. To set an address, supply all five of address_street, address_city, address_state, address_zip_code, and address_country together (address_street2 is optional — include it only for a second line); a partial address is rejected.
| Parameter | Type | Required | Description |
|---|---|---|---|
first_name | string | yes | |
last_name | string | yes | |
email | string | no | |
middle_name | string | no | Middle name or initial, up to 60 characters. MyCase keeps one middle-name field per person: client responses carry it as both middle_name and middle_initial, and leads and staff call it middle_initial. |
cell_phone_number | string | no | |
work_phone_number | string | no | |
home_phone_number | string | no | |
fax_phone_number | string | no | |
notes | string | no | |
birthdate | string | no | Date of birth as an ISO8601 date, e.g. '1979-04-23'. Two rejections to avoid, both returned as a 422: any other format (only ISO8601 parses), and a date that is not already past — today itself is rejected, not just future dates, with 'must be in the past.' This field is date-only: a full ISO8601 timestamp is accepted rather than rejected, but its time and offset are silently discarded instead of converted, so a UTC-normalized datetime can land on the wrong calendar day — send the bare date. |
people_group_id | integer | no | People group ID |
contact_group | string | no | People group to file the client under, given by NAME — the same groups list_people_groups returns, matched on the trimmed, case-insensitive name. An unrecognized name is NOT rejected: MyCase silently files the client under the firm's default 'client' group instead, so confirm the name with list_people_groups first, or use people_group_id, which does reject an id the firm does not have. Mutually exclusive with people_group_id — sending both is rejected. An empty or blank string is treated as omitting the param. Omit both this and people_group_id and the new client is filed under the firm's default 'client' group. |
case_ids | array[integer] | no | Case IDs to associate |
address_street | string | no | |
address_street2 | string | no | |
address_city | string | no | |
address_state | string | no | |
address_zip_code | string | no | |
address_country | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to a client, set on the new client. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Acme"},{"custom_field":{"id":43},"value":"2026-01-05"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields with filter_parent_type='client'; a field defined for another record type is rejected. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
create_client_note
Create Client Note write beta
Create a note on a client.
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | integer | yes | |
note | string | yes | Note content |
date | string | yes | Note date (ISO8601 date). MyCase requires it and has no default; for a note written now, pass today's date in the user's own time zone. |
subject | string | no |
get_client
Get Client read-only
Get a specific client by ID. A client that came from a converted lead keeps the lead's referral_source (name), referral_source_reference ({id}) and referred_by ({id} of the referring client or company; the id does not say which, so try get_client, then get_company). Each is null when the client did not come from a lead or the lead had no value for it; all three are omitted when the user cannot view leads.
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | integer | yes |
list_client_cases
List Client Cases read-only
List cases for a specific client. outstanding_balance is the case's full flat-fee amount (the same value as get_case_billing_summary's fee_structure.flat_fee): the agreed flat fee, or a balance entered when the case was created or imported. It does not go down as that fee is invoiced or paid, so do not report it as the current balance. For what is owed on a case, use get_case_billing_summary: unbilled.flat_fees is the part of the flat fee not yet invoiced, and totals.outstanding is the unpaid balance on the case's invoices. The Outstanding Balance Total on a MyCase client or company page adds both up across that contact's open cases, plus unpaid retainer requests, and leaves out draft invoices, which totals.outstanding includes (list_case_invoices shows each invoice's status and balance_due).
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | integer | yes | |
filter_status | string | no | One of: open, closed. |
filter_updated_after | string | no | |
field_client | array[string] | no | Client fields to add to every nested client reference on each case — the contacts and clients entries, and billing_contact when the case is billed to a client. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. Use it to read client details straight off a page of cases instead of calling get_client once per client id. Cost: each field is serialized once per reference, so twice for every client on the case plus once more for its billing contact — on a full page of cases, asking for six fields makes a very large response. Request only the fields you will use; id is always present. If your MyCase user cannot view clients, this tool is refused outright: it resolves a client by id before reading any cases. One of: id, first_name, last_name, middle_initial, middle_name, email, contact_group, birthdate, cell_phone_number, work_phone_number, home_phone_number, fax_phone_number, updated_at, created_at. |
field_custom_field | array[string] | no | Custom-field attributes to add to the nested custom_field reference inside each custom_field_values entry. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. field_type is the only value MyCase accepts here, so this widens {id} to {id, field_type} and no further — for a custom field's name, parent type, or list options, call get_custom_field or list_custom_fields. One of: field_type. |
page_size | integer | no | Items per page (default 25, max 200) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_client_message_threads
List Client Message Threads read-only
List message threads for a client.
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | integer | yes | |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_client_notes
List Client Notes read-only
List notes for a client.
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | integer | yes | |
filter_status | string | no | One of: active, archived. |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_clients
List Clients read-only
List all clients. Supports filtering by name, email, phone, and text search. A client that came from a converted lead keeps the lead's referral_source (name), referral_source_reference ({id}) and referred_by ({id} of the referring client or company; the id does not say which, so try get_client, then get_company). Each is null when the client did not come from a lead or the lead had no value for it; all three are omitted when the user cannot view leads.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
filter_email | string | no | |
filter_first_name | string | no | |
filter_last_name | string | no | |
filter_cell_phone_number | string | no | |
filter_work_phone_number | string | no | |
filter_home_phone_number | string | no | |
filter_fax_phone_number | string | no | |
search_starts_with | string | no | |
field_custom_field | array[string] | no | Custom-field attributes to add to the nested custom_field reference inside each custom_field_values entry. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. field_type is the only value MyCase accepts here, so this widens {id} to {id, field_type} and no further — for a custom field's name, parent type, or list options, call get_custom_field or list_custom_fields. One of: field_type. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_client
Update Client write destructive beta
Update an existing client. case_ids REPLACES the client's full set of case associations — cases not included are unlinked; omit it to leave case associations unchanged. To add this client to one case without touching their other cases, use add_clients_to_case instead. To change the address, supply all five of address_street, address_city, address_state, address_zip_code, and address_country together; a partial address is rejected. Changing the address REPLACES it in full — include address_street2 to keep an existing second line, since omitting it clears any current second line. Omit all address fields to leave the existing address unchanged. custom_field_values is not one of these replace-in-full params: it is an upsert keyed on custom field id, so a field you include overwrites only that field, and a field you omit keeps its current value.
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | integer | yes | |
first_name | string | no | |
last_name | string | no | |
email | string | no | |
middle_name | string | no | Middle name or initial, up to 60 characters. MyCase keeps one middle-name field per person: client responses carry it as both middle_name and middle_initial, and leads and staff call it middle_initial. |
cell_phone_number | string | no | |
work_phone_number | string | no | |
home_phone_number | string | no | |
fax_phone_number | string | no | |
notes | string | no | |
birthdate | string | no | Date of birth as an ISO8601 date, e.g. '1979-04-23'. Two rejections to avoid, both returned as a 422: any other format (only ISO8601 parses), and a date that is not already past — today itself is rejected, not just future dates, with 'must be in the past.' This field is date-only: a full ISO8601 timestamp is accepted rather than rejected, but its time and offset are silently discarded instead of converted, so a UTC-normalized datetime can land on the wrong calendar day — send the bare date. |
people_group_id | integer | no | People group ID |
contact_group | string | no | People group to file the client under, given by NAME — the same groups list_people_groups returns, matched on the trimmed, case-insensitive name. An unrecognized name is NOT rejected: MyCase silently files the client under the firm's default 'client' group instead, so confirm the name with list_people_groups first, or use people_group_id, which does reject an id the firm does not have. Mutually exclusive with people_group_id — sending both is rejected. An empty or blank string is treated as omitting the param. Omit it to leave the client's current group unchanged. |
case_ids | array[integer] | no | |
address_street | string | no | |
address_street2 | string | no | |
address_city | string | no | |
address_state | string | no | |
address_zip_code | string | no | |
address_country | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to this client. This is an UPSERT keyed on custom field id, not a replacement of the client's whole set: a field you include OVERWRITES its current value, and a field you OMIT is LEFT UNCHANGED. Omitting a field does not clear it, so send only the fields you are changing — do not re-send a field's existing value to preserve it, and omit the param entirely when you are changing no custom fields. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Acme"},{"custom_field":{"id":43},"value":"2026-01-05"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields with filter_parent_type='client'; a field defined for another record type is rejected. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
Comments
list_case_comments
List Case Comments read-only
List the comments left on a case's records — the internal comment threads MyCase shows on tasks, documents, and calendar events. These are staff commentary attached to a record, NOT client-portal messages (list_message_threads / list_thread_messages), NOT case notes (list_case_notes), and NOT internal staff chat (list_case_chat_conversations). Each comment returns comment_text, the author's name, created_at/updated_at, its own id, a type of TaskComment / DocumentComment / AppointmentComment, and a parent anchor {type, id} naming the record it sits on — open a Task or Document parent with get_task or get_document. No tool opens an Appointment parent: its id names a single calendar occurrence, not an event, so passing it to get_event 404s or returns an unrelated event. Quote parent.id rather than the comment's own id when telling the user which record is being discussed. All three kinds come back interleaved, oldest first by insertion order (ascending id); narrow to one kind with filter_comment_type. Comments deleted in MyCase are excluded. An empty list means 'none you can see', not 'none on this case': what is returned follows your own MyCase permissions, and comments on calendar events are limited to events you are a participant on.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | |
filter_comment_type | string | no | Return only comments on one kind of parent record. The values are the parent's lowercase name, so 'appointment' means a calendar event and selects the rows whose parent.type comes back as 'Appointment'. Omit it for all three kinds. One of: task, document, appointment. |
filter_updated_after | string | no | Only comments whose own updated_at is strictly after this ISO8601 timestamp (exclusive bound); a timestamp with no UTC offset is read as UTC, and a non-ISO8601 value is rejected upstream. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
Companies
add_clients_to_company
Add Clients To Company write beta
Add clients to a company. Additive — existing linked clients are preserved (unlike update_company, whose client_ids replaces).
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | integer | yes | |
client_ids | array[integer] | yes | Client IDs to add |
create_company
Create Company write beta
Create a new company in MyCase. To set an address, supply all five of address_street, address_city, address_state, address_zip_code, and address_country together (address_street2 is optional — include it only for a second line); a partial address is rejected.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Company name |
website | string | no | |
email | string | no | |
fax_phone_number | string | no | |
main_phone_number | string | no | |
notes | string | no | |
client_ids | array[integer] | no | Client IDs to associate |
case_ids | array[integer] | no | Case IDs to associate |
address_street | string | no | |
address_street2 | string | no | |
address_city | string | no | |
address_state | string | no | |
address_zip_code | string | no | |
address_country | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to a company, set on the new company. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Acme"},{"custom_field":{"id":43},"value":"2026-01-05"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields with filter_parent_type='company'; a field defined for another record type is rejected. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
create_company_note
Create Company Note write beta
Create a note on a company.
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | integer | yes | |
note | string | yes | |
date | string | yes | Note date (ISO8601 date). MyCase requires it and has no default; for a note written now, pass today's date in the user's own time zone. |
subject | string | no |
get_company
Get Company read-only
Get a specific company by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | integer | yes |
list_companies
List Companies read-only
List all companies. Supports filtering by status, name, email, phone, and text search.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_status | string | no | Filter by company status. One of: active, archived. |
filter_updated_after | string | no | |
filter_email | string | no | |
filter_name | string | no | |
filter_fax_phone_number | string | no | |
filter_main_phone_number | string | no | |
search_starts_with | string | no | |
field_custom_field | array[string] | no | Custom-field attributes to add to the nested custom_field reference inside each custom_field_values entry. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. field_type is the only value MyCase accepts here, so this widens {id} to {id, field_type} and no further — for a custom field's name, parent type, or list options, call get_custom_field or list_custom_fields. One of: field_type. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_company_notes
List Company Notes read-only
List notes for a company.
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | integer | yes | |
filter_status | string | no | One of: active, archived. |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_company
Update Company write destructive beta
Update an existing company. client_ids and case_ids each REPLACE the company's full set of that association — members not included are removed; omit a list param to leave that association unchanged. To add without removing existing ones, use add_clients_to_company (clients) or add_companies_to_case (cases). To change the address, supply all five of address_street, address_city, address_state, address_zip_code, and address_country together; a partial address is rejected. Changing the address REPLACES it in full — include address_street2 to keep an existing second line, since omitting it clears any current second line. Omit all address fields to leave the existing address unchanged. custom_field_values is not one of these replace-in-full params: it is an upsert keyed on custom field id, so a field you include overwrites only that field, and a field you omit keeps its current value.
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | integer | yes | |
name | string | no | |
website | string | no | |
email | string | no | |
fax_phone_number | string | no | |
main_phone_number | string | no | |
notes | string | no | |
client_ids | array[integer] | no | |
case_ids | array[integer] | no | |
address_street | string | no | |
address_street2 | string | no | |
address_city | string | no | |
address_state | string | no | |
address_zip_code | string | no | |
address_country | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to this company. This is an UPSERT keyed on custom field id, not a replacement of the company's whole set: a field you include OVERWRITES its current value, and a field you OMIT is LEFT UNCHANGED. Omitting a field does not clear it, so send only the fields you are changing — do not re-send a field's existing value to preserve it, and omit the param entirely when you are changing no custom fields. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Acme"},{"custom_field":{"id":43},"value":"2026-01-05"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields with filter_parent_type='company'; a field defined for another record type is rejected. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
Contacts
list_contacts
List Contacts read-only
List all contacts (clients, companies, leads) across the firm.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
Custom Fields
create_custom_field
Create Custom Field write beta
Create a new custom field definition.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Field name |
field_type | string | yes | One of: boolean, currency, date, list, long_text, numeric, short_text. |
parent_type | string | yes | One of: case, client, company, expense, time, time_and_expense. |
option_values | array[string] | no | Option values — required when field_type is 'list', omit otherwise |
create_custom_field_options
Create Custom Field Options write beta
Add one or more options to a list-type custom field in one call. To rename a single existing option, use update_custom_field_option.
| Parameter | Type | Required | Description |
|---|---|---|---|
custom_field_id | integer | yes | |
option_values | array[string] | yes | Option values to add |
get_custom_field
Get Custom Field read-only
Get a specific custom field definition by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
custom_field_id | integer | yes |
list_custom_field_options
List Custom Field Options read-only
List options for a list-type custom field.
| Parameter | Type | Required | Description |
|---|---|---|---|
custom_field_id | integer | yes | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_custom_fields
List Custom Fields read-only
List all custom field definitions.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_parent_type | string | no | One of: case, client, company, expense, time, time_and_expense. |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_custom_field
Update Custom Field write destructive beta
Rename a custom field definition. Only the name can be changed; a list field's options are preserved (use update_custom_field_option to change an option's value).
| Parameter | Type | Required | Description |
|---|---|---|---|
custom_field_id | integer | yes | |
name | string | yes | New field name |
update_custom_field_option
Update Custom Field Option write destructive beta
Update one existing option on a list-type custom field. To add new options, use create_custom_field_options, which takes a list.
| Parameter | Type | Required | Description |
|---|---|---|---|
custom_field_id | integer | yes | |
option_id | integer | yes | |
option_value | string | yes | New option value |
Documents
create_document
Create Document write beta
Create a new document record and get a presigned upload URL — step 1 of a two-step upload. This call sends metadata only (no file content). The response includes the new document's id plus top-level put_url and put_headers fields. Step 2: upload the file content yourself with an HTTP PUT of the raw bytes to put_url, sending every header in put_headers exactly as returned; if put_headers does not include a Content-Type, also send 'Content-Type: application/octet-stream' (the content type the URL is signed for). The document is visible via get_document once created.
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | no | Folder path in the firm's Documents storage, e.g. 'Templates'; missing folders are created. Omit to file the document at the firm's root folder. |
filename | string | yes | |
description | string | no | |
staff_ids | array[integer] | no | Staff IDs to assign |
assigned_date | string | no | The document's own date, as an ISO8601 date, e.g. '2026-01-05'. Despite the name this is the document's datestamp — the date the document itself carries — and NOT a date on which the document was assigned to anyone. Only ISO8601 parses: any other format, including the US-style 01/05/2026, is rejected with a 422, 'assigned_date is invalid'. This field is date-only: a full ISO8601 timestamp is accepted rather than rejected, but its time and offset are silently discarded instead of converted, so a UTC-normalized datetime can land on the wrong calendar day — send the bare date. |
create_document_version
Create Document Version write beta
Create a new version of an existing document and get a presigned upload URL — step 1 of a two-step upload. Takes no body params: the new version keeps the document's current filename, and its file content comes from step 2. The response includes the new version's id and version number plus top-level put_url and put_headers fields. Step 2: upload the file content yourself with an HTTP PUT of the raw bytes to put_url, sending every header in put_headers exactly as returned; if put_headers does not include a Content-Type, also send 'Content-Type: application/octet-stream' (the content type the URL is signed for). Verify with list_document_versions.
| Parameter | Type | Required | Description |
|---|---|---|---|
document_id | integer | yes |
download_document
Download Document read-only
Get the raw file content of a document. Returns a short-lived presigned download URL (redirect_url) to the raw file in storage; if you cannot fetch URLs, pass stream_content=true to receive the bytes in this response instead, in chunks — UTF-8 text as text, anything else base64. The file is returned as-is — for server-extracted text, use get_document_text instead.
| Parameter | Type | Required | Description |
|---|---|---|---|
document_id | integer | yes | |
stream_content | boolean | no | Set true to have the server fetch the file and return its content in this response instead of a URL — use it when you cannot fetch URLs yourself. Content arrives one chunk per call: UTF-8 text as encoding 'utf-8' in 'text', anything else (including text in other charsets) as encoding 'base64' in 'data', with content_type, content_length and filename alongside. encoding is classified from each chunk's own bytes, so re-read it on every chunk rather than assuming the first chunk's value holds. Each base64 chunk is padded on its own: decode every chunk's data separately and join the bytes, never the strings. When next_offset is a number, call again with offset set to it for the next chunk; null means the file is complete. |
offset | integer | no | Byte offset to start the chunk at — pass the previous response's next_offset. Default 0. Only valid with stream_content=true. |
max_bytes | integer | no | Chunk size ceiling in bytes (default 65536, max 1048576). Only valid with stream_content=true. |
download_document_version
Download Document Version read-only
Get the raw file content of a specific document version. Returns a short-lived presigned download URL (redirect_url) to the raw file in storage; if you cannot fetch URLs, pass stream_content=true to receive the bytes in this response instead, in chunks — UTF-8 text as text, anything else base64. The file is returned as-is — for server-extracted text of the latest parsed version, use get_document_text instead.
| Parameter | Type | Required | Description |
|---|---|---|---|
document_id | integer | yes | |
version_number | integer | yes | |
stream_content | boolean | no | Set true to have the server fetch the file and return its content in this response instead of a URL — use it when you cannot fetch URLs yourself. Content arrives one chunk per call: UTF-8 text as encoding 'utf-8' in 'text', anything else (including text in other charsets) as encoding 'base64' in 'data', with content_type, content_length and filename alongside. encoding is classified from each chunk's own bytes, so re-read it on every chunk rather than assuming the first chunk's value holds. Each base64 chunk is padded on its own: decode every chunk's data separately and join the bytes, never the strings. When next_offset is a number, call again with offset set to it for the next chunk; null means the file is complete. |
offset | integer | no | Byte offset to start the chunk at — pass the previous response's next_offset. Default 0. Only valid with stream_content=true. |
max_bytes | integer | no | Chunk size ceiling in bytes (default 65536, max 1048576). Only valid with stream_content=true. |
get_document
Get Document read-only
Get document metadata by ID. Returns metadata only — use download_document for the raw file or get_document_text for extracted text.
| Parameter | Type | Required | Description |
|---|---|---|---|
document_id | integer | yes |
get_document_text
Get Document Text read-only
Get server-extracted plain text for a document. Returns a short-lived URL to the extracted text plus metadata (content_length, page_count, parsed_at). Fetch the URL promptly with your available tools — it expires in about 5 minutes and serves text/plain. If you cannot fetch URLs, pass stream_content=true to receive the text in this response instead, in chunks (follow next_offset). For large documents, prefer downloading and searching locally over reading the whole text into context. Text reflects the document version current when it was parsed (see parsed_at); newly uploaded versions are not re-extracted. When no extracted text exists, returns not_found with a detail field — error 'no_parsed_text' plus a parsing_status. When that status is 'not_parsed', a detail parsing_enabled of false means text extraction is not enabled for this firm and no parse will ever run: do not retry, and use download_document for the raw file instead. Otherwise 'not_parsed' means extraction has not completed for the current version and may still be in progress for a recently uploaded document; 'deferred_parser', 'quota_blocked_backparse' and 'quota_blocked_new_upload' are also still awaiting a parse (retry later). Every other status — 'failed', 'unparseable', 'not_supported', 'scanned', 'ocr_disabled', 'large_document', 'old_document', and 'parsed' when its extracted text object is missing — will not change on retry: use download_document for the raw file instead. Detail error 'not_found' means the document itself does not exist.
| Parameter | Type | Required | Description |
|---|---|---|---|
document_id | integer | yes | |
stream_content | boolean | no | Set true to have the server fetch the file and return its content in this response instead of a URL — use it when you cannot fetch URLs yourself. Content arrives one chunk per call: UTF-8 text as encoding 'utf-8' in 'text', anything else (including text in other charsets) as encoding 'base64' in 'data', with content_type, content_length and filename alongside. encoding is classified from each chunk's own bytes, so re-read it on every chunk rather than assuming the first chunk's value holds. Each base64 chunk is padded on its own: decode every chunk's data separately and join the bytes, never the strings. When next_offset is a number, call again with offset set to it for the next chunk; null means the file is complete. |
offset | integer | no | Byte offset to start the chunk at — pass the previous response's next_offset. Default 0. Only valid with stream_content=true. |
max_bytes | integer | no | Chunk size ceiling in bytes (default 65536, max 1048576). Only valid with stream_content=true. |
list_document_versions
List Document Versions read-only
List all versions of a document.
| Parameter | Type | Required | Description |
|---|---|---|---|
document_id | integer | yes | |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_documents
List Documents read-only
List all documents across the firm. Returns document metadata only — use get_document_text for extracted text or download_document for the raw file.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_folder_documents
List Folder Documents read-only
List documents within a folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
folder_id | integer | yes | |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_folder_subfolders
List Folder Subfolders read-only
List subfolders within a folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
folder_id | integer | yes | |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_document
Update Document write destructive beta
Update document metadata. staff_ids REPLACES the document's full assigned-staff list — staff not included are unassigned; omit it to leave staff unchanged. Staff cannot be updated on a document that belongs to a case.
| Parameter | Type | Required | Description |
|---|---|---|---|
document_id | integer | yes | |
path | string | no | New folder path for the document, e.g. 'Templates/Retainers'; missing folders are created. Omit to leave the document in its current folder — unlike create_document, omitting it here does NOT move the document to the root. |
filename | string | no | |
description | string | no | |
staff_ids | array[integer] | no | |
assigned_date | string | no | The document's own date, as an ISO8601 date, e.g. '2026-01-05'. Despite the name this is the document's datestamp — the date the document itself carries — and NOT a date on which the document was assigned to anyone. Only ISO8601 parses: any other format, including the US-style 01/05/2026, is rejected with a 422, 'assigned_date is invalid'. This field is date-only: a full ISO8601 timestamp is accepted rather than rejected, but its time and offset are silently discarded instead of converted, so a UTC-normalized datetime can land on the wrong calendar day — send the bare date. |
Events
add_staff_to_event
Add Staff To Event write beta
Add staff members to an event. Additive — existing attendees are preserved (unlike update_event, whose staff_ids replaces). New attendees join as optional unless listed in required_staff_ids, and staff already on the event keep their current setting. A recurring event cannot be updated with this tool, because the change would apply to every occurrence; edit its attendees in the MyCase app.
| Parameter | Type | Required | Description |
|---|---|---|---|
event_id | integer | yes | |
staff_ids | array[integer] | yes | Staff IDs to add |
required_staff_ids | array[integer] | no | Staff IDs, all also in staff_ids, who must attend (the Attend box in MyCase). When you pass it, every other staff_ids entry is marked optional. That includes anyone already on the event whom you list again; leave them out of staff_ids to keep their setting. |
create_event
Create Event write beta
Create a new calendar event. Timed events' start/end are instants, returned in UTC (Z); show them to the user in get_me's time_zone. All-day events' start/end are calendar dates (YYYY-MM-DD) with no zone, and end is the last day, inclusive.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Event name |
start | string | yes | Start (ISO8601). Timed: a local time with the UTC offset get_me's time_zone has on that date, e.g. 2026-09-29T18:30:00-06:00 (Denver in summer; -07:00 in winter); a time without an offset is read as UTC. All-day (all_day=true): the calendar date, e.g. 2026-09-29; any time or offset is dropped, not converted. |
end | string | yes | End (ISO8601), same format as start. All-day: the last day, inclusive, so a one-day event has end equal to start. |
staff_ids | array[integer] | yes | Staff member IDs to invite |
required_staff_ids | array[integer] | no | Staff IDs, all also in staff_ids, who must attend (the Attend box in MyCase). When you pass it, every other staff_ids entry is marked optional. Omit it and every attendee is optional. |
case_id | integer | no | Associated case ID |
location_id | integer | no | Location ID |
all_day | boolean | no | Whether this is an all-day event |
get_event
Get Event read-only
Get a specific event by ID. When starting from a full_text_search result, pass its appointment_rule_id — an event search result's id identifies a single occurrence on the calendar, drawn from a different table, so passing it here usually 404s and can otherwise return an unrelated event. Each staff entry carries required: true for an attendee who must attend. Timed events' start/end are instants, returned in UTC (Z); show them to the user in get_me's time_zone. All-day events' start/end are calendar dates (YYYY-MM-DD) with no zone, and end is the last day, inclusive.
| Parameter | Type | Required | Description |
|---|---|---|---|
event_id | integer | yes |
list_events
List Events read-only
List all calendar events. Use filter_occurs_after/filter_occurs_before to answer calendar questions -- without them (and without filter_updated_after) this returns the firm's whole history, most recently updated first. The pair is an overlap window: an event running across it is returned even though it started earlier, so a multi-day trial or filing period is covered as well as all-day and timed events. Bounds are inclusive. A recurring event matches on its own dates only, not on its later occurrences. filter_staff_id matches the staff array. Each staff entry carries required: true for an attendee who must attend. Timed events' start/end are instants, returned in UTC (Z); show them to the user in get_me's time_zone. All-day events' start/end are calendar dates (YYYY-MM-DD) with no zone, and end is the last day, inclusive. For a day or week, write both bounds as the user's local times with the UTC offset get_me's time_zone has on those dates, e.g. 2026-09-29T00:00:00-06:00 to 2026-09-29T23:59:59-06:00, not converted to Z: all-day events are matched on the date each bound is written in, so a Z-converted end bound also returns the next day's all-day events.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
filter_occurs_after | string | no | Only events still running at or after this ISO8601 datetime -- compares the event's end, so one that began earlier still matches; the bound is inclusive. Write it in the user's local offset, not converted to Z; a time without an offset is read as UTC |
filter_occurs_before | string | no | Only events that begin at or before this ISO8601 datetime; the bound is inclusive. Send it WITH filter_occurs_after -- on its own it also matches every older event, because anything that started earlier also begins before this bound. Write it in the user's local offset, not converted to Z; a time without an offset is read as UTC |
filter_staff_id | integer | no | Only events this staff member is on (see list_staff) |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_event
Update Event write destructive beta
Update an existing event. staff_ids REPLACES the event's full staff attendee list — staff not included are removed; omit it to leave attendees unchanged. To add staff without removing existing attendees, use add_staff_to_event. A recurring event cannot be updated with this tool; edit it in the MyCase app. Timed events' start/end are instants, returned in UTC (Z); show them to the user in get_me's time_zone. All-day events' start/end are calendar dates (YYYY-MM-DD) with no zone, and end is the last day, inclusive.
| Parameter | Type | Required | Description |
|---|---|---|---|
event_id | integer | yes | |
name | string | no | |
start | string | no | Start (ISO8601). Timed: a local time with the UTC offset get_me's time_zone has on that date, e.g. 2026-09-29T18:30:00-06:00 (Denver in summer; -07:00 in winter); a time without an offset is read as UTC. All-day (all_day=true): the calendar date, e.g. 2026-09-29; any time or offset is dropped, not converted. |
end | string | no | End (ISO8601), same format as start. All-day: the last day, inclusive, so a one-day event has end equal to start. |
staff_ids | array[integer] | no | |
required_staff_ids | array[integer] | no | Staff IDs, all also in staff_ids, who must attend (the Attend box in MyCase). When you pass it, every other staff_ids entry is marked optional. Omit it and attendees already on the event keep their current setting, while new ones join as optional. |
case_id | integer | no | |
location_id | integer | no | |
all_day | boolean | no |
Expenses
create_expense
Create Expense write beta
Create a new expense entry.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | ID of the case this expense belongs to |
activity_name | string | yes | Expense activity name |
billable | boolean | yes | Whether this expense is billable |
entry_date | string | yes | Expense date (ISO8601) |
cost | number | yes | Cost per unit |
units | number | yes | Number of units |
staff_id | integer | no | Staff member ID |
description | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to an expense, set on the new expense. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Matter code A-17"},{"custom_field":{"id":43},"value":"1250.00"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields: a field applies to an expense only when its parent_type is 'expense' or 'time_and_expense', and one defined for another record type is rejected. filter_parent_type matches the stored type exactly, so filter_parent_type='expense' does NOT return the 'time_and_expense' fields an expense also accepts — call list_custom_fields once per parent type. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
get_expense
Get Expense read-only
Get a specific expense by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
expense_id | integer | yes |
list_all_expenses
List All Expenses read-only
List all expenses across the firm. Optionally filter by case.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_case_id | integer | no | Filter expenses to a specific case ID |
filter_updated_after | string | no | |
field_custom_field | array[string] | no | Custom-field attributes to add to the nested custom_field reference inside each custom_field_values entry. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. field_type is the only value MyCase accepts here, so this widens {id} to {id, field_type} and no further — for a custom field's name, parent type, or list options, call get_custom_field or list_custom_fields. One of: field_type. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_expense
Update Expense write destructive beta
Update an expense. Partial update — only the provided params change; omitted params keep their current values. Cannot update an expense already associated with an invoice.
| Parameter | Type | Required | Description |
|---|---|---|---|
expense_id | integer | yes | |
case_id | integer | no | ID of the case this expense belongs to |
activity_name | string | no | Expense activity name |
billable | boolean | no | |
entry_date | string | no | Expense date (ISO8601) |
cost | number | no | Cost per unit |
units | number | no | Number of units |
staff_id | integer | no | Staff member ID |
description | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to this expense. This is an UPSERT keyed on custom field id, not a replacement of the expense's whole set: a field you include OVERWRITES its current value, and a field you OMIT is LEFT UNCHANGED. Omitting a field does not clear it, so send only the fields you are changing — do not re-send a field's existing value to preserve it, and omit the param entirely when you are changing no custom fields. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Matter code A-17"},{"custom_field":{"id":43},"value":"1250.00"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields: a field applies to an expense only when its parent_type is 'expense' or 'time_and_expense', and one defined for another record type is rejected. filter_parent_type matches the stored type exactly, so filter_parent_type='expense' does NOT return the 'time_and_expense' fields an expense also accepts — call list_custom_fields once per parent type. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
Feedback
report_feedback
Report Feedback write
Report a bug or request a feature for the MyCase MCP server itself. Call this when a tool fails repeatedly or behaves unexpectedly, or when the user wants a capability these tools don't provide. Confirm with the user before submitting. Do NOT include client names, case details, or document contents — describe the problem generically.
| Parameter | Type | Required | Description |
|---|---|---|---|
feedback_type | string | yes | One of: bug, feature_request. |
summary | string | yes | One-line description of the problem or request |
details | string | no | Expected vs. actual behavior, and exact error text if any |
related_tool | string | no | Name of the MCP tool this feedback concerns |
Firm
get_firm
Get Firm read-only
Get information about the current firm, including its offices (id, name, primary, phones, address) — the source of office IDs for create_case and update_case.
get_me
Get Me read-only
Get information about the currently authenticated user. time_zone is the IANA zone (e.g. America/Denver) MyCase shows this user's calendar in; there is no firm-level zone. Use its UTC offset for datetimes the user means locally, such as "today" or "9am".
Invoices
get_invoice
Get Invoice read-only
Get a specific invoice by ID, including invoices that have online payments disabled: the by-id lookup uses the same wide invoice scope as list_all_invoices, so any invoice that appears there can also be fetched here.
| Parameter | Type | Required | Description |
|---|---|---|---|
invoice_id | integer | yes |
list_all_invoices
List All Invoices read-only
List invoices (bills) across the firm, or scope to a single case with filter_case_id. At this tool's default only_allowed_online_payments=false, filter_case_id returns the same rows as list_case_invoices for the same caller, balance-forwarded bills (status 'forwarded') included. Requires the caller's billing permission and returns 403 without it. But unlike list_case_invoices, which 404s a case the caller cannot see, an unknown filter_case_id here returns an empty list — so an empty result is not proof that the case exists or that it has no invoices. Confirm the case with get_case before reporting either. Case visibility and bill visibility are separate rules: a caller without all-cases access still sees the bills shared with them, so this tool can return rows for a case list_case_invoices 404s.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_case_id | integer | no | Only invoices on this case — the server-side case scope, so one case's invoices need no client-side filtering. It composes with the caller's own authorization scope and never widens it: a caller who cannot view all firm cases still sees only the bills shared with them. Setting only_allowed_online_payments true alongside it drops invoices that have online payments disabled, narrowing below what list_case_invoices shows. |
filter_updated_after | string | no | Only records updated strictly after this ISO8601 timestamp (exclusive bound) |
only_allowed_online_payments | boolean | no | When true, returns only invoices that allow online payments. Defaults to false so all invoices are listed. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_all_payments
List All Payments read-only
List payment transactions across ALL payable types — bill, retainer, subscription, and quick-bill payments — including pending and failed rows (pass filter_status to narrow); each row's payable_type names its kind. Ordered by payment date, most recent first; payments sharing a date come back newest-recorded first, and a payment with no date sorts last, after every dated payment. amount is SIGNED: refunds/voids are negative with is_refund true, so they net out rather than inflate a total. To total true settled receipts, sum only status=success rows — pending and failed rows are returned here by default and never settled. Scope to one case with filter_case_id (spanning every payable type and status for that case) or to one invoice with filter_payable_id. For a case's SETTLED BILL receipts only — retainer, subscription, and quick-bill receipts and pending/failed transactions excluded — use list_case_billing_payments instead; the two feeds agree on amount sign and both report client as the payable's billing contact (never the staff member who recorded the payment), differing in payable-type breadth, status breadth, and order — list_case_billing_payments returns oldest-recorded first rather than by payment date. For payments in a period ("this month", "last 30 days"), pass filter_pay_date_after / filter_pay_date_before. For incremental sync of changes (status changes, refunds, backdated entries), pass filter_updated_after; it is slow on firms with a long payment history, so never use it to find recent payments.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_payable_id | integer | no | Filter by invoice ID |
filter_case_id | integer | no | Filter to payments whose payable (bill/retainer/subscription/quick-bill) is on this case — the server-side case scope for firm-wide payment analysis, so a single case's payments need no client-side assembly. Spans all payable types and statuses for the case; list_case_billing_payments is the bill-only, successful-only case feed. |
filter_status | string | no | Filter by payment status. One of: pending, success, failure, error, timeout. |
filter_updated_after | string | no | ISO8601 datetime; returns only payments whose payment record's own updated_at is strictly after this bound (exclusive). Keys off the payment transaction itself — an update to the parent invoice/bill does not surface its payments. Slow on large firms; for a date range use filter_pay_date_after / filter_pay_date_before. |
filter_pay_date_after | string | no | Only payments dated on or after this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive. Undated payments are excluded. |
filter_pay_date_before | string | no | Only payments dated on or before this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive. Undated payments are excluded. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
Leads
convert_lead
Convert Lead write destructive beta
Convert a lead into a client and open their potential case as a real case (the MyCase intake workflow). The lead becomes a client with the same ID, and the opened case is returned. A lead still pending review is approved as part of the conversion. Conversion cannot be undone.
| Parameter | Type | Required | Description |
|---|---|---|---|
lead_id | integer | yes | |
case_name | string | no | Name for the opened case. Defaults to the potential case's existing name (usually "Potential Case: <lead name>"), so prefer providing a real case name. |
create_lead
Create Lead write beta
Create a new lead in MyCase. This lands two records, not one: MyCase opens a potential case for the lead, and that case's id comes back as the lead's case reference. A potential case is not a regular case on this surface: list_cases never returns it and get_case 404s on its id; convert_lead is what turns it into a real case. Read its notes with list_lead_notes, not list_case_notes. To set an address, supply all five of address_street, address_city, address_state, address_zip_code, and address_country together (address_street2 is optional — include it only for a second line); a partial address is rejected.
| Parameter | Type | Required | Description |
|---|---|---|---|
first_name | string | yes | |
last_name | string | yes | |
middle_initial | string | no | Middle name or initial: 1-60 letters, no spaces or punctuation. Despite the name it is the same MyCase field clients call middle_name. |
email | string | no | |
cell_phone_number | string | no | |
work_phone_number | string | no | |
home_phone_number | string | no | |
birthdate | string | no | Date of birth as an ISO8601 date, e.g. '1979-04-23'. Two rejections to avoid, both returned as a 422: any other format (only ISO8601 parses), and a date that is not already past — today itself is rejected, not just future dates, with 'must be in the past.' This field is date-only: a full ISO8601 timestamp is accepted rather than rejected, but its time and offset are silently discarded instead of converted, so a UTC-normalized datetime can land on the wrong calendar day — send the bare date. |
drivers_license_number | string | no | |
drivers_license_state | string | no | |
referral_source_id | integer | no | Referral source ID |
referral_source_name | string | no | Referral source by name (mutually exclusive with referral_source_id). An unrecognized name is NOT rejected: MyCase files the lead under the firm's 'Other' referral source instead, so confirm the exact name with list_referral_sources first. An empty or blank string is treated as omitting the param. |
referred_by_id | integer | no | ID of the client or company that referred this lead |
lead_details | string | no | |
address_street | string | no | |
address_street2 | string | no | |
address_city | string | no | |
address_state | string | no | |
address_zip_code | string | no | |
address_country | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to a lead, set on the new lead. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Acme"},{"custom_field":{"id":43},"value":"2026-01-05"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Leads use the firm's CLIENT custom fields — MyCase defines no lead-specific custom field type. Get ids from list_custom_fields with filter_parent_type='client'; a field defined for another record type is rejected. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
get_lead
Get Lead read-only
Get a specific lead by ID. The lead's case reference is the potential case MyCase paired with it. A potential case is not a regular case on this surface: list_cases never returns it and get_case 404s on its id; convert_lead is what turns it into a real case. Read its notes with list_lead_notes, not list_case_notes.
| Parameter | Type | Required | Description |
|---|---|---|---|
lead_id | integer | yes |
list_lead_notes
List Lead Notes read-only
List the notes on a lead: the Notes tab under the lead's Case Details in MyCase, where intake staff record consultation notes and next steps. Each note names the lead it belongs to. Notes stay with the case when the lead is converted; read them with list_case_notes after convert_lead.
| Parameter | Type | Required | Description |
|---|---|---|---|
lead_id | integer | yes | |
filter_status | string | no | One of: active, archived. |
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_leads
List Leads read-only
List all leads. Supports filtering by status, name, email, phone, and text search. Each lead's case reference is the potential case MyCase paired with it. A potential case is not a regular case on this surface: list_cases never returns it and get_case 404s on its id; convert_lead is what turns it into a real case. Read its notes with list_lead_notes, not list_case_notes.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_status | string | no | One of: approved, unapproved. |
filter_updated_after | string | no | |
filter_email | string | no | |
filter_first_name | string | no | |
filter_last_name | string | no | |
filter_cell_phone_number | string | no | |
filter_work_phone_number | string | no | |
filter_home_phone_number | string | no | |
search_starts_with | string | no | |
field_custom_field | array[string] | no | Custom-field attributes to add to the nested custom_field reference inside each custom_field_values entry. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. field_type is the only value MyCase accepts here, so this widens {id} to {id, field_type} and no further — for a custom field's name, parent type, or list options, call get_custom_field or list_custom_fields. One of: field_type. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_lead
Update Lead write destructive beta
Update an existing lead. To change the address, supply all five of address_street, address_city, address_state, address_zip_code, and address_country together; a partial address is rejected. Changing the address REPLACES it in full — include address_street2 to keep an existing second line, since omitting it clears any current second line. Omit all address fields to leave the existing address unchanged. custom_field_values is not one of these replace-in-full params: it is an upsert keyed on custom field id, so a field you include overwrites only that field, and a field you omit keeps its current value.
| Parameter | Type | Required | Description |
|---|---|---|---|
lead_id | integer | yes | |
first_name | string | no | |
last_name | string | no | |
status | string | no | Pipeline status, by name. Each firm configures its own statuses, so there is no fixed set of values; get_lead and list_leads show a lead's current status. A name that matches none of the firm's active statuses is rejected with a 422 whose message lists the firm's active statuses, and the lead keeps the status it had. An archived status counts as no match. The lead must already be approved, and create_lead makes a lead that is still pending review: setting status on such a lead refuses the whole update with a 400, so approve the lead in MyCase first. Omit status to leave it unchanged; an empty or blank string is treated as omitting the param. |
middle_initial | string | no | Middle name or initial: 1-60 letters, no spaces or punctuation. Despite the name it is the same MyCase field clients call middle_name. |
email | string | no | |
cell_phone_number | string | no | |
work_phone_number | string | no | |
home_phone_number | string | no | |
birthdate | string | no | Date of birth as an ISO8601 date, e.g. '1979-04-23'. Two rejections to avoid, both returned as a 422: any other format (only ISO8601 parses), and a date that is not already past — today itself is rejected, not just future dates, with 'must be in the past.' This field is date-only: a full ISO8601 timestamp is accepted rather than rejected, but its time and offset are silently discarded instead of converted, so a UTC-normalized datetime can land on the wrong calendar day — send the bare date. |
drivers_license_number | string | no | |
drivers_license_state | string | no | |
referral_source_id | integer | no | |
referral_source_name | string | no | Referral source by name (mutually exclusive with referral_source_id). An unrecognized name is NOT rejected: MyCase files the lead under the firm's 'Other' referral source instead, so confirm the exact name with list_referral_sources first. An empty or blank string is treated as omitting the param. |
referred_by_id | integer | no | ID of the client or company that referred this lead |
lead_details | string | no | |
address_street | string | no | |
address_street2 | string | no | |
address_city | string | no | |
address_state | string | no | |
address_zip_code | string | no | |
address_country | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to this lead. This is an UPSERT keyed on custom field id, not a replacement of the lead's whole set: a field you include OVERWRITES its current value, and a field you OMIT is LEFT UNCHANGED. Omitting a field does not clear it, so send only the fields you are changing — do not re-send a field's existing value to preserve it, and omit the param entirely when you are changing no custom fields. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Acme"},{"custom_field":{"id":43},"value":"2026-01-05"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Leads use the firm's CLIENT custom fields — MyCase defines no lead-specific custom field type. Get ids from list_custom_fields with filter_parent_type='client'; a field defined for another record type is rejected. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
Locations
create_location
Create Location write beta
Create a new location. To set an address, supply all five of address_street, address_city, address_state, address_zip_code, and address_country together (address_street2 is optional — include it only for a second line); a partial address is rejected.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Location name |
address_street | string | no | |
address_street2 | string | no | |
address_city | string | no | |
address_state | string | no | |
address_zip_code | string | no | |
address_country | string | no |
get_location
Get Location read-only
Get a specific location by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
location_id | integer | yes |
list_locations
List Locations read-only
List all locations.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
search_starts_with | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_location
Update Location write destructive beta
Update a location. To change the address, supply all five of address_street, address_city, address_state, address_zip_code, and address_country together; a partial address is rejected. Changing the address REPLACES it in full — include address_street2 to keep an existing second line, since omitting it clears any current second line. Omit all address fields to leave the existing address unchanged.
| Parameter | Type | Required | Description |
|---|---|---|---|
location_id | integer | yes | |
name | string | no | |
address_street | string | no | |
address_street2 | string | no | |
address_city | string | no | |
address_state | string | no | |
address_zip_code | string | no | |
address_country | string | no |
Messages
create_message_thread
Create Message Thread write destructive beta
Create a new message thread. Provide at least one recipient via client_recipient_ids and/or staff_recipient_ids.
| Parameter | Type | Required | Description |
|---|---|---|---|
subject | string | yes | Message thread subject |
body | string | yes | First message body |
client_recipient_ids | array[integer] | no | Client recipient IDs (at least one of client_/staff_recipient_ids) |
staff_recipient_ids | array[integer] | no | Staff recipient IDs (at least one of client_/staff_recipient_ids) |
get_message_thread
Get Message Thread read-only
Get a message thread's metadata by ID: subject, sender, participants (clients and staff), related case, and the opening message body. Use list_thread_messages to read the full conversation.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | integer | yes |
list_message_threads
List Message Threads read-only
List message threads the authorizing user can view, most recently active first, including staff-only threads (list_client_message_threads only covers threads with a specific client). Returns thread metadata plus the opening message; use list_thread_messages to read a full conversation.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_last_post_after | string | no | ISO8601 datetime. Returns threads whose most recent message was posted after this time. A thread edited but not posted to is not returned, nor is one with no messages yet. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_thread_messages
List Thread Messages read-only
List every message in a message thread — the opening message and all replies, oldest first — with each message's sender and timestamp.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | integer | yes | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
reply_to_message_thread
Reply To Message Thread write destructive beta
Reply to an existing message thread. Use list_thread_messages first to read the conversation you are replying to.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | integer | yes | |
body | string | yes | Reply message body |
Notes
get_note
Get Note read-only
Get a specific note by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
note_id | integer | yes |
update_note
Update Note write destructive beta
Update an existing note. Cannot update archived notes.
| Parameter | Type | Required | Description |
|---|---|---|---|
note_id | integer | yes | |
note | string | no | Note content |
subject | string | no | |
date | string | no | Note date (ISO8601) |
People Groups
create_people_group
Create People Group write beta
Create a new people group. Name must be unique.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | People group name (must be unique) |
get_people_group
Get People Group read-only
Get a specific people group by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
people_group_id | integer | yes |
list_people_groups
List People Groups read-only
List all people groups (contact groups).
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_people_group
Update People Group write destructive beta
Update a people group. Cannot update system groups.
| Parameter | Type | Required | Description |
|---|---|---|---|
people_group_id | integer | yes | |
name | string | no |
Practice Areas
create_practice_area
Create Practice Area write beta
Create a new practice area.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Practice area name |
get_practice_area
Get Practice Area read-only
Get a specific practice area by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
practice_area_id | integer | yes |
list_practice_areas
List Practice Areas read-only
List all practice areas.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
search_starts_with | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_practice_area
Update Practice Area write destructive beta
Update a practice area.
| Parameter | Type | Required | Description |
|---|---|---|---|
practice_area_id | integer | yes | |
name | string | no |
Referral Sources
create_referral_source
Create Referral Source write beta
Create a new referral source.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Referral source name |
get_referral_source
Get Referral Source read-only
Get a specific referral source by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
referral_source_id | integer | yes |
list_referral_sources
List Referral Sources read-only
List all referral sources.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_updated_after | string | no | |
search_starts_with | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
Search
full_text_search
Full Text Search read-only
Search across the firm's records by text query: cases, contacts, documents, notes, tasks, events, leads, messages, expenses, and call log entries. Matches names, descriptions, numbers, and notes, and returns matched-text highlights. Only record types the connection is authorized to read are searched; the response's requested_types field lists the types the search was submitted with, narrowed by scope and by case_id, and is not proof each one was searched — a type the firm has disabled is dropped upstream without being echoed back. Pass case_id to confine the search to a single case instead of the whole firm. Event results are keyed by appointment_rule_id, not by id — pass appointment_rule_id to get_event.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | yes | Search text, 3-100 characters |
types | array[string] | no | Record types to search. Defaults to every type the connection can read. Searching 'documents' alone also matches document file content where the firm has content search enabled. One of: calls, cases, contacts, documents, events, expenses, leads, messages, notes, tasks. |
case_id | integer | no | Scope the search to one case: only records associated with that case are returned. Firm-level documents — those filed outside any case — are excluded, so omit case_id to reach them. contacts and leads cannot be case-scoped: they are dropped from a case-scoped search and requested_types echoes the reduced list, and asking for only contacts or leads together with case_id is rejected. A message's case association can be stale, so message hits are best-effort. A case id that does not exist, or that this user cannot see, returns an empty result set rather than an error — indistinguishable from a case that simply has no matches, so do not read empty results as proof the case is empty. Where the firm's search index cannot apply the case filter, the scope is enforced after each upstream page is fetched, so a page may return fewer than page_size results — even zero — while later pages still hold matches. Judge that by the token and never by page length: a short page still carries pagination.next_page_token when more results remain. |
page_size | integer | no | Results per page (default 10, max 50) |
page_token | string | no | Token from the previous response's pagination.next_page_token, to fetch the next page. Pass it back with the same query, types and case_id it was issued for; a token replayed against a different search is rejected rather than answered. page_size is not part of that match — it may be changed mid-sweep, and the new value applies from that page on. A response with no next_page_token is the last page. |
Staff
get_staff
Get Staff read-only
Get a specific staff member by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
staff_id | integer | yes |
list_staff
List Staff read-only
List all staff members.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_status | string | no | One of: active, inactive. |
filter_updated_after | string | no | |
search_starts_with | string | no | |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
Tasks
add_staff_to_task
Add Staff To Task write beta
Add staff members to a task. Additive — existing assignees are preserved (unlike update_task, whose staff_ids replaces).
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | integer | yes | |
staff_ids | array[integer] | yes | Staff member IDs to add |
create_task
Create Task write beta
Create a new task in MyCase.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Task name |
priority | string | no | Task priority. Defaults to "None" (no priority). One of: None, Low, Medium, High. |
staff_ids | array[integer] | yes | List of staff member IDs to assign |
description | string | no | |
due_date | string | no | Due date in ISO8601 format |
completed | boolean | no | |
case_id | integer | no | Case ID to associate the task with |
get_task
Get Task read-only
Get a specific task by ID. A task on a lead's potential case carries a lead reference in place of case -- resolve it with get_lead, not get_case.
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | integer | yes |
list_tasks
List Tasks read-only
List all tasks. Use filter_completed=false with filter_due_before to answer "what is due" -- without them this returns the firm's whole task history. filter_completed=false includes tasks awaiting review, matching the open task list in the app. A task on a lead's potential case carries a lead reference in place of case -- resolve it with get_lead, not get_case.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_case_id | integer | no | Filter tasks to a specific case ID |
filter_updated_after | string | no | ISO8601 datetime to filter tasks updated after this time |
filter_due_before | string | no | Only tasks due on or before this ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_completed | boolean | no | true -> only completed tasks; false -> only open ones, which includes tasks awaiting review |
filter_staff_id | integer | no | Only tasks this staff member is assigned (see list_staff) |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_task
Update Task write destructive beta
Update an existing task. staff_ids REPLACES the task's full assigned-staff list — staff not included are unassigned; omit it to leave staff unchanged. To add staff without removing existing assignees, use add_staff_to_task.
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | integer | yes | |
name | string | no | |
priority | string | no | One of: None, Low, Medium, High. |
staff_ids | array[integer] | no | List of staff member IDs to assign |
description | string | no | |
due_date | string | no | |
completed | boolean | no | |
case_id | integer | no |
Texts
get_text_conversation
Get Text Conversation read-only
Get one SMS conversation's metadata by id: the external phone number, the linked contact (id, name, and whether they are a Client or a Lead), and when it last moved. Message bodies are not included — use list_text_conversation_messages. Archived conversations are returned, with archived_at set. Returns 404 if the conversation does not exist, belongs to another firm, or is with a lead you cannot view; those are deliberately indistinguishable, so do not report a 404 as proof the conversation does not exist.
| Parameter | Type | Required | Description |
|---|---|---|---|
text_conversation_id | integer | yes |
list_text_conversation_messages
List Text Conversation Messages read-only
Read the individual text messages in one SMS conversation. Get the conversation's id from list_text_conversations. Each message carries its body, a direction of 'incoming' or 'outgoing', the staff member who sent an outgoing one, the delivery status, and the time it was sent or received — which is the field to order by, not created_at, since a queued message can be delivered later than it was written. Text threads run long: production conversations commonly hold a few hundred messages and the largest hold over a thousand, so page with page_size and page_token rather than assuming one call is the whole history, and say so if you are summarizing only part of it. MMS attachments are listed by id and content type only — no URL and no filename. An image or PDF sent by text can be reported as present and counted, never opened, downloaded, or read. Treat that as settled rather than a gap waiting on a tool: the media is held by the SMS carrier and MyCase stores only the carrier's own link, so there is nothing here to hand back safely. Point the user at the conversation in MyCase to view an attachment instead of implying a retrieval path exists.
| Parameter | Type | Required | Description |
|---|---|---|---|
text_conversation_id | integer | yes | |
filter_updated_after | string | no | Only messages whose own updated_at is strictly after this ISO8601 timestamp (exclusive bound); a timestamp with no UTC offset is read as UTC, and a non-ISO8601 value is rejected upstream. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_text_conversations
List Text Conversations read-only
List the firm's two-way SMS/text conversations — real text messages to and from phones, exchanged with clients, leads and unrecognized numbers. This is NOT MyCase's secure client-portal messaging (list_message_threads) and NOT internal staff chat (list_case_chat_conversations); all three use the word 'message' and share nothing else. Returns conversation metadata only (id, external_phone_number, the linked contact's id/name/type, last_message_at, archived_at, timestamps); message bodies are NOT included — pass a conversation's id to list_text_conversation_messages to read them. The list is firm-wide, not per-case: a text conversation is keyed to a phone number and a contact, and carries no case at all. To answer 'what texts are on this case', list the case's clients first and then pass one of their ids as filter_client_id — and say plainly that this is a per-contact answer, since the same conversation belongs to every case that contact is on. Conversations with no linked contact (an inbound text from an unrecognized number) are returned and simply carry no client. Archived conversations are excluded by default; pass include_archived=true to list them too, as the MyCase app's 'show archived' toggle does. An archived row carries archived_at. An empty list means 'none in your firm that you may see', not 'this firm does not text'. Requires the MyCase text-messaging permission; without it every text tool returns a permission error rather than an empty list. Conversations with a LEAD are withheld from users who cannot view leads.
| Parameter | Type | Required | Description |
|---|---|---|---|
include_archived | boolean | no | true → also return archived conversations; omitted or false → active conversations only. |
filter_client_id | integer | no | Only conversations linked to this contact — a client or lead id, not a case id. |
filter_updated_after | string | no | Only conversations whose own updated_at is strictly after this ISO8601 timestamp (exclusive bound); a timestamp with no UTC offset is read as UTC. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
Time Entries
create_time_entry
Create Time Entry write beta
Create a new time entry.
| Parameter | Type | Required | Description |
|---|---|---|---|
case_id | integer | yes | ID of the case this time entry belongs to |
activity_name | string | yes | Time entry activity name |
entry_date | string | yes | Entry date (ISO8601) |
staff_id | integer | yes | Staff member ID |
rate | number | no | Rate. When flat_fee is true this is the flat-fee total charged, not an hourly rate; otherwise the hourly rate. Omit it to use the rate MyCase would fill in: the activity's own rate (a flat-fee activity makes a flat-fee entry at its fee), else the staff member's rate on the case (case rate, rate plan or default rate). Required when flat_fee is true and the activity has no flat fee. When you omit it, tell the user what was applied: the response's rate (when visible), flat_fee and total. |
hours | number | no | Number of hours (required by the API unless flat_fee is true) |
billable | boolean | no | |
flat_fee | boolean | no | If true, hours is optional |
description | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to a time entry, set on the new time entry. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Matter code A-17"},{"custom_field":{"id":43},"value":"1250.00"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields: a field applies to a time entry only when its parent_type is 'time' or 'time_and_expense', and one defined for another record type is rejected. filter_parent_type matches the stored type exactly, so filter_parent_type='time' does NOT return the 'time_and_expense' fields a time entry also accepts — call list_custom_fields once per parent type. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
get_time_entry
Get Time Entry read-only
Get a specific time entry by ID. Includes the server-computed total (always present); billing_status ('unbilled', 'on_draft_invoice', or 'invoiced') and the invoices list are included only with billing access (the caller's billing permission plus the read_invoices scope) and are withheld otherwise.
| Parameter | Type | Required | Description |
|---|---|---|---|
time_entry_id | integer | yes |
list_all_time_entries
List All Time Entries read-only
List all time entries across the firm. Optionally filter by case. Results filtered by entry date come back newest entry date first. Each entry carries a server-computed total that prices flat-rate entries correctly — prefer it over hours x rate math; total is always present. With billing access (the caller's billing permission plus the read_invoices scope) each entry also carries a billing_status of 'unbilled', 'on_draft_invoice', or 'invoiced' and an invoices list reflecting actual invoice association including draft invoices; without that access those billing fields are withheld, so their absence does not mean an entry is unbilled. billed does NOT imply the entry was billable — check the billable field when reconciling. Flat-fee billing line items are separate records that never appear in time-entry results — use list_case_billing_flat_fees for a case's flat fees; billed flat fees also appear in get_case_invoice_line_items, but unbilled flat-fee revenue appears only via list_case_billing_flat_fees, so a work-in-progress total built without it misses that revenue.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_case_id | integer | no | Filter time entries to a specific case ID |
filter_updated_after | string | no | Only entries updated strictly after this ISO8601 timestamp (exclusive bound, unlike the inclusive entry-date filters) |
filter_entry_date_after | string | no | Only entries dated on or after this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_entry_date_before | string | no | Only entries dated on or before this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_billed | boolean | no | true → only entries already on an invoice (draft or finalized — use billing_status to distinguish); false → only entries not yet invoiced |
field_custom_field | array[string] | no | Custom-field attributes to add to the nested custom_field reference inside each custom_field_values entry. Nested records are returned as {id} only; each name listed here is added to them. This grows the response — it cannot be used to drop fields. field_type is the only value MyCase accepts here, so this widens {id} to {id, field_type} and no further — for a custom field's name, parent type, or list options, call get_custom_field or list_custom_fields. One of: field_type. |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
update_time_entry
Update Time Entry write destructive beta
Update a time entry. Partial update — only the provided params change; omitted params keep their current values. Cannot update a time entry already associated with an invoice.
| Parameter | Type | Required | Description |
|---|---|---|---|
time_entry_id | integer | yes | |
case_id | integer | no | ID of the case this time entry belongs to |
activity_name | string | no | Time entry activity name |
entry_date | string | no | Entry date (ISO8601) |
staff_id | integer | no | Staff member ID |
rate | number | no | Rate. When flat_fee is true this is the flat-fee total charged, not an hourly rate; otherwise the hourly rate. |
hours | number | no | Number of hours (required by the API unless flat_fee is true) |
billable | boolean | no | |
flat_fee | boolean | no | |
description | string | no | |
custom_field_values | string | no | Values for the custom fields that apply to this time entry. This is an UPSERT keyed on custom field id, not a replacement of the time entry's whole set: a field you include OVERWRITES its current value, and a field you OMIT is LEFT UNCHANGED. Omitting a field does not clear it, so send only the fields you are changing — do not re-send a field's existing value to preserve it, and omit the param entirely when you are changing no custom fields. Pass a JSON array encoded as a string, e.g. '[{"custom_field":{"id":42},"value":"Matter code A-17"},{"custom_field":{"id":43},"value":"1250.00"}]' — each element is an object with a custom_field object holding the field's integer id plus that field's value. Get ids from list_custom_fields: a field applies to a time entry only when its parent_type is 'time' or 'time_and_expense', and one defined for another record type is rejected. filter_parent_type matches the stored type exactly, so filter_parent_type='time' does NOT return the 'time_and_expense' fields a time entry also accepts — call list_custom_fields once per parent type. Values go in as strings: dates as ISO8601 (YYYY-MM-DD), currency/numeric as decimal strings such as '1250.00', booleans as 'true'/'false', and a list field's value must match one of its options exactly (list_custom_field_options). A custom field may appear at most once per call — a repeated id is rejected. |
Trust
list_trust_account_balances
List Trust Account Balances read-only
Current per-client trust (IOLTA) holdings — the rows behind the MyCase Trust Account Summary report. Each row is one client's balance in one trust bank account, either allocated to a case or unallocated: unallocated=true rows (case is null) are client-wide trust funds not tied to any case. The holder in client may be a company, not only a person: client.type is Client, Company, or Lead, so look a holder up with get_client, get_company, or get_lead to match (get_client returns not_found for a company). A client's total trust = the sum of their rows, which equals list_trust_ledger_entries net change summed over that client's ENTIRE unfiltered history — never a date-filtered ledger sum. A case's trust picture = its allocated rows PLUS its clients' unallocated rows, which get_case_billing_summary.balances.trust also reports; the two are independent reads that use different case-allocation predicates, so they usually agree but may legitimately differ — treat them as corroborating, not a guaranteed identity. Balances are current as of now (no as-of-date support); for changes over a period use list_trust_ledger_entries. Zero-balance rows are included unless filter_include_zero_balances=false. Trust money is client money, never firm revenue. Requires the billing user permission and returns 403 without it.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_client_id | integer | no | Only holdings of this holder (client or company) ID |
filter_case_id | integer | no | Only holdings ALLOCATED to this case — excludes the clients' unallocated (client-wide) rows, so this alone understates the case's available trust |
filter_bank_account_id | integer | no | Only holdings in this trust bank account ID |
filter_include_zero_balances | boolean | no | false → omit zero-balance rows (clients whose trust is fully disbursed); default true |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_trust_bank_accounts
List Trust Bank Accounts read-only
List the firm's trust (IOLTA) bank accounts with the total client funds currently held in each. balance is CLIENT money the firm holds in trust — never firm revenue; do not mix it with billing totals (invoices, payments, collected). balance equals the sum of list_trust_account_balances rows for the account, and clients_with_balance counts distinct clients with a nonzero balance. Deleted accounts are excluded. Requires the billing user permission and returns 403 without it — an empty list really means the firm has no trust accounts.
| Parameter | Type | Required | Description |
|---|---|---|---|
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |
list_trust_ledger_entries
List Trust Ledger Entries read-only
Trust (IOLTA) ledger activity — the rows of the MyCase Trust Account Activity report: deposits into trust and payments/withdrawals out of trust, including the incoming deposits that invoice-payment feeds never show. Each entry carries credit (client funds INTO trust) or debit (funds OUT of trust) as non-negative amounts with exactly one nonzero; net change over a set = sum(credit) - sum(debit). That net change equals a client's current trust balance ONLY when summing ALL of that client's entries with no date filter (their entire unfiltered history); a date-windowed sum is just the net change for that window, not the as-of-now balance — for current balances use list_trust_account_balances. No running balance is served. client is the holder and may be a company: route by client.type (Client, Company, Lead) to get_client, get_company, or get_lead. case is null for activity on a client's unallocated (client-wide) trust; related_to names the invoice, retainer request, or subscription the money was applied to, when any. Refunds and voided checks appear as opposite-side entries (a voided trust withdrawal shows as a credit), never as deletions. Results are ordered by id (creation order), not by date — use the inclusive filter_pay_date_after/_before bounds for date windows. Trust money is client money, never firm revenue — do not sum it into billing totals. Requires the billing user permission and returns 403 without it, so an empty list really means no matching trust activity.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_client_id | integer | no | Only entries for this holder (client or company) ID |
filter_case_id | integer | no | Only entries on trust buckets allocated to this case; excludes the clients' unallocated (client-wide) activity |
filter_bank_account_id | integer | no | Only entries touching this trust bank account ID |
filter_pay_date_after | string | no | Only entries dated on or after this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_pay_date_before | string | no | Only entries dated on or before this date-only ISO8601 date (YYYY-MM-DD); the bound is inclusive |
filter_updated_after | string | no | Only records updated strictly after this ISO8601 timestamp (exclusive bound, unlike the inclusive pay-date filters) |
page_size | integer | no | Items per page (default 25, max 1000) |
page_token | string | no | pagination.next_page_token from the previous page. It carries that page's filters, so other args may be omitted; changing a filter mid-walk is rejected. |