June 3, 2026 · 11 min read

The Clio Custom Field API Trap: Value-Instance ID Is Not the Field-Definition ID

We built the open-source Clio and MyCase MCP connectors. Along the way we hit the same custom-field bug everyone hits, plus a set of field-type and upload gotchas the docs gloss over. Here's the field guide we wish we'd had.

If you are writing Clio custom fields through the Manage API and your PATCH either errors out or quietly updates the wrong field, the cause is almost always the same. The id you are sending is not the id Clio wants.

This is the single most common Clio integration bug we have seen, and it cost us time too. We build software for law firms and legal tech founders, and we maintain the open-source Clio MCP connector and a matching MyCase connector that let Claude talk to a practice management system directly. Writing custom-field values cleanly meant getting this exact detail right. Here is what we learned, written as the reference we would have wanted on day one.


What is the value-instance ID vs the field-definition ID in Clio?

Clio has two different objects that both have ids, and they are easy to confuse.

The custom field definition is the field itself: "Client Mailing Address", a free-text field, defined once at the account level. It has an id, referenced in the API as custom_field:{id}.

The custom field value is the specific value stored on one matter for one of those definitions. "123 Main St" living on matter #4471 is a value record, and it has its own separate id. This is the value-instance id.

When you PATCH a matter to update a custom field, the id at the top of each entry in the custom_field_values array is the value-instance id, not the definition id. The definition id goes inside a nested custom_field object. Send the definition id in the top-level id slot and you are pointing at a value record that probably does not exist, or worse, the id collides with a different value record and you overwrite something unrelated.

The rule, stated plainly:

  • Updating an existing value: send the value-instance id as the top-level id, plus the new value.
  • Creating a new value on a matter that has none yet: omit id entirely, and supply custom_field: { id: definitionId } instead.
  • Deleting a value: send the value-instance id with _destroy: true.

The practical consequence: you cannot write a custom field by definition id alone. You first have to read the matter and find out whether a value record already exists for that field. If it does, you grab its value-instance id and update. If it does not, you create. There is no shortcut where you say "set field X on matter Y to Z" in one stateless call. You read, then you write.

How to read custom field values first

Custom field values are not returned by default. You have to ask for them with a fields parameter that expands the nested object, something close to GET /matters/{id}?fields=custom_field_values{id,value,custom_field{id,name,field_type}}. That single read gives you everything you need to decide create vs update, and it tells you the field type, which matters for the next section.


Why does my Clio custom field write get rejected even with the right ID?

You fixed the id, the PATCH goes through, and the value still does not stick. Now you are hitting the field-type wall. Clio custom fields are typed, and each type enforces its own format rules. A value that is valid for one type is rejected by another.

Clio Custom Field Types: What Each One Accepts

Field type Accepts Common rejection
Free text Any string None, this is the easy one
Picklist Only predefined options, each capped around 55 characters A free-text string that is not an existing option
Currency Whole-number amounts Decimal values
Date Valid, well-formed dates Free-form date strings, ambiguous formats

This is the part that breaks naive "just normalize all my fields" projects. If a firm has a few hundred custom fields populated inconsistently, you cannot run one blanket pass over them. A picklist field will reject any value that is not already in its option list. A currency field rejects decimals. A date field rejects anything it cannot parse. So the first real step in any custom-field normalization job is an inventory: pull every field, group by type, and decide the rule per type before you write a single value.

For free-text fields you genuinely can do smart cleanup, including using an LLM to standardize addresses or naming formats. For picklists you have to map incoming values onto the existing options, or extend the option list deliberately. For currency and date you validate and coerce, or you flag the row for a human. There is no architecture where one of these strategies covers all four types, so the honest answer to "can you normalize my custom fields" is "yes, after we inventory them by type."


The Clio rate limit will define your batch design

Clio Manage uses OAuth 2.0 and rate-limits conservatively. Plan around roughly 3 requests per second per application. Older documentation references a 50-per-minute legacy figure, and either way the lesson is the same: this is not an API you hammer.

Now do the math on a normalization job. Every field write needs at least one read to find the value-instance id and one PATCH to set it. Across hundreds of matters with hundreds of fields each, you are looking at thousands of round trips at a few per second. That is minutes to hours, not seconds. The design that survives:

  • Throttle deliberately and honor the X-RateLimit-* response headers rather than guessing.
  • Back off on HTTP 429 with retry, do not just fail the run.
  • Paginate at 200 records per page.
  • Make the runner resumable, so a network blip at matter 1,400 of 2,000 does not restart everything.
  • Build a dry-run preview and per-matter rollback before you let it write to anything real. Nobody should trust a script writing to hundreds of fields without seeing the diff first.

One more access gotcha: the OAuth user running the job needs write access to every matter you intend to touch. A run that succeeds on the matters the connecting user owns and silently skips the rest is a worse outcome than a clean failure, because you think you are done when you are not.


How do you upload a document to Clio? It is a three-step S3 flow

If your integration generates a document and needs to put it back into a matter, do not look for a simple multipart upload. Clio uses a presigned-S3 pattern in three steps:

  1. POST to /documents to register the document and the matter it belongs to. The response includes a presigned put_url.
  2. PUT the raw bytes directly to that put_url (it points at S3, not at Clio).
  3. PATCH the document with fully_uploaded: true to commit it.

Miss the final PATCH and you get an orphaned upload that never becomes visible in the matter. This is the step people skip, and then they spend an afternoon wondering why the file uploaded but is not there. Reads are simpler: you download document content via a version URL. Our connector ships robust upload today, which is the step that closes the loop on any "draft a document, write it back to Clio" workflow.


There is no Clio document webhook, so how do you trigger on a new doc?

Clio webhooks cover activity, bill, calendar_entry, communication, contact, matter, and task. There is no document webhook. Clio's own document automation (Clio Draft) is UI-only, with no API to trigger generation either. So if your workflow needs to react when a document appears, you have two honest options:

  • Poll the documents endpoint on a schedule and diff against what you have seen.
  • Subscribe to the matter updated webhook and trigger on the stage change, since a matter reaching a litigation stage is usually the cause of the document being generated. You react to the cause, not the missing effect.

Either way, remember Clio webhooks auto-expire, 3 days by default and 31 days maximum, so a long-lived integration has to renew them or it goes silent without an error.


Does the same pattern apply to MyCase and PracticePanther?

Custom fields are a near-universal feature in legal practice management. Clio, MyCase, and PracticePanther all let firms define them, and all three have the same underlying shape: a field definition that lives at the account level, and value records that live on each matter or case. The vocabulary differs, but the trap is the same one. The id you use to write a value is the value record's id, not the definition's id, and most of these APIs type their fields, which means picklist and date constraints follow you across platforms.

We built the MyCase connector after the Clio one specifically so we would not have to relearn this per platform. The lesson generalizes: when you connect any AI workflow to a legal system of record, the hard part is rarely "can the model write a good draft." It is the systems plumbing underneath, the id semantics, the type validation, the rate limits, the upload commit step, the missing webhook. That is where integrations quietly break in production. Tools like n8n can orchestrate the calls, and Anthropic's Claude can draft the content, but neither one knows that your picklist field will reject a free-text string until it already did.


One more thing: data residency and the human-review gate

Two non-API points that bite legal integrations specifically, and that we get asked about constantly.

Residency. Clio runs a Canadian region (ca.app.clio.com, aligned with PIPEDA), so a Canadian firm's account may live there and your integration must call the correct regional base URL. Anthropic's Claude API, by contrast, has no Canadian region and stores at rest in the US. If you are moving Canadian client data through a model, the realistic posture is an organization-level Zero Data Retention configuration on the Anthropic API (which is obtainable even by a small firm, and is separate from the consumer Team chat product, where ZDR is not available), a US inference pin, and a plainly documented cross-border disclosure. Do not assume the chat product and the API behave the same way. They do not.

Human review is not optional. The Law Society of Ontario's 2024 guidance, conceptually aligned with ABA Formal Opinion 512, points the same direction: confidentiality, competence, supervision, and a duty to verify AI outputs. A lawyer has to review what the model produced. So bake the review step into the workflow as a hard gate, not a nice-to-have. The integration drafts; the lawyer approves. Any architecture that auto-files an AI-generated document into a matter without a review checkpoint is solving the wrong problem.


Frequently Asked Questions

Why does my Clio custom field PATCH return an error or update the wrong field?

The most common cause is sending the custom field definition id where Clio expects the value-instance id. In a custom_field_values array, the top-level id of each entry is the id of the existing value record on that matter, not the id of the field definition. The definition id goes inside a nested custom_field object. To create a new value, omit id and supply the custom_field reference instead.

Does the Clio API support reading and writing custom field values?

Yes. Read with a GET on the matter using a fields parameter that expands custom_field_values, and write with a PATCH on the matter carrying a nested custom_field_values array. There is no practical cap on field count, so a matter with several hundred custom fields is fine. The constraint is field type, not field count.

What is the Clio API rate limit?

Treat it as roughly 3 requests per second per application; older docs also cite a 50-per-minute legacy figure. Throttle, honor the X-RateLimit-* headers, back off on HTTP 429, and paginate at 200 records per page. Any batch touching hundreds of matters is rate-limit bound, so make it resumable.

How do you upload a document to Clio via the API?

It is a three-step presigned-S3 flow. POST to /documents to register and get a put_url, PUT the raw bytes to that url, then PATCH the document with fully_uploaded: true to commit. Skip the final PATCH and you get an orphaned, invisible upload.

Does Clio have a webhook for new documents?

No. Webhooks exist for activity, bill, calendar_entry, communication, contact, matter, and task. To detect a generated document, poll the documents endpoint or subscribe to the matter updated webhook and trigger on the stage change that caused generation. Clio webhooks auto-expire (3 days default, 31 max), so renew them.


Next Step

We hit every one of these gotchas building the Clio and MyCase MCP connectors, which is why we can scope a Clio automation in days instead of discovering the field-type wall in week three. If you are wiring Claude or another model into Clio, MyCase, or PracticePanther, and you want the integration to survive contact with a real firm's data, we are happy to talk through the architecture.

No pitch deck. Just an honest technical conversation about what the API will and will not let you do.

Book a free architecture review →

Or explore more from our legal tech practice:

Legal Tech

Related Articles

View all Legal Tech articles ➔

Book a Call