Most people who integrate with Clio Manage hit the same wall in the same order. Reading data is easy. Creating a matter, logging a time entry, writing a note, all straightforward. Then they try to push a document back into a matter, reach for a normal multipart/form-data POST the way you would with almost any other API, and nothing works.
Clio does not accept the file in the POST. It hands you a URL and tells you to upload the bytes somewhere else.
We learned this the hard way building our Clio MCP connector (the open-source bridge that lets Claude read and write a law firm's Clio data). The upload path was the single most fiddly piece of the whole connector, and the official docs gloss over the part that trips everyone up. This post is the write-up we wish existed when we started.
How do you upload a document to Clio via the API?
Clio Manage uses a three-call sequence to upload a document. It is not one request. It is three, and they go to two different hosts.
The full upload sequence:
- POST /documents with metadata (matter id, filename, content type). Clio responds with a document record and a short-lived presigned S3
put_url. - PUT the raw bytes to that
put_url. This request goes to Amazon S3, not to Clio's API host, and it does not carry your Clio auth headers. - PATCH the document with
fully_uploaded: true. This tells Clio the bytes landed. Without it, the document stays pending and never appears in the matter.
That third call is the one that bites people. The PUT to S3 succeeds, you get a clean 200, you assume you're done, and then the document is nowhere in the Clio UI. It exists as a metadata stub with no confirmed payload. Clio is waiting for you to say "the upload finished." Until you PATCH fully_uploaded, it treats the record as incomplete.
So the mental model is: call one reserves a slot and gives you a place to put the file, call two puts the file there, call three commits the transaction.
Why a presigned S3 URL instead of a normal file POST?
Clio stores document payloads in Amazon S3. Rather than proxy every byte of every file through their own API tier, they generate a presigned URL and let your client upload directly to S3. For a 40MB scanned PDF, that's the right call: the file never touches Clio's application servers, and it never counts against your API throughput.
That last point matters more than it sounds. Clio's rate limit is conservative, roughly 3 requests per second per app (their docs also reference an older 50-requests-per-minute figure). If every document upload streamed through the API, a batch job would saturate that budget instantly. Pushing bytes straight to S3 keeps the limit free for the metadata calls that actually need it.
The tradeoff is the one you'd expect. You now manage three calls instead of one, and the PUT is authenticated by the presigned URL's own signature, not by your OAuth token. Send your Authorization header on that PUT and S3 may reject it. Forget to honor the content type you declared in step one and the signature mismatch fails the upload. The presigned URL is also short-lived, so if you generate it, queue the job, and PUT thirty minutes later, expect a failure and re-request from step one.
This pattern is not unique to Clio. MyCase and other practice-management platforms lean on presigned object storage for the same reasons, and if you've ever uploaded directly to S3 from a browser you've seen the shape before. What's frustrating with Clio specifically is the confirm step, because it's easy to miss and the failure is silent.
What we ship: Our connector exposes a single upload_document tool that wraps all three calls, handles the S3 PUT as a robust multipart transfer, and only reports success after the fully_uploaded PATCH returns. The caller (a person, or Claude acting on their behalf) never sees the three-step machinery. That's the whole point of wrapping it once and getting it right.
The decision: when do you even need this flow?
Before you build the upload path, be honest about whether you need it at all. There are three valid architectures here, and the right one depends on your workflow, not on what's most impressive.
Three ways to get a document into Clio
| Approach | Best when | Cost |
|---|---|---|
| Three-call API upload | Programmatic, repeated, generated files (a drafting pipeline) | You build and maintain the flow |
| Clio Draft document automation | Template-driven forms inside Clio's UI | No API to trigger it; UI-only |
| Manual upload | Low volume, one-off documents | A human's time, every time |
The middle row catches people off guard. Clio Draft (the document-automation product) generates documents from templates, but it's UI-only. There is no API to trigger generation or fill a template programmatically. So if your plan was "let Clio Draft build the document and I'll just kick it off from my code," that plan doesn't exist. You generate the file yourself (from a template, from an LLM, from whatever) and push it in via the three-call flow.
The API upload is worth building only when the document is produced repeatedly by software: a consult note assembled from a transcript, a status letter generated when a matter changes stage, a batch export. For a partner who saves one PDF a week, automation is pure overhead. Diagnose the volume first.
There is no document webhook. Plan for it.
If your pipeline needs to react when a document appears (say, a generated draft you then want to populate or move), here's the hard fact: Clio has no document webhook.
The webhook events Clio does support are: activity, bill, calendar_entry, communication, contact, matter, and task. Documents are not on that list. So you have two honest options:
- Poll. Periodically list documents on the matters you care about and diff against what you've seen. Simple, but you eat the rate limit and add latency.
- Subscribe to the
matterupdated webhook and trigger on the cause. In most firms, a document gets generated because a matter hit a litigation stage. The stage change is the real signal. Watch the matter, react to the stage change, and you've effectively caught the document event without a document event.
One more trap: Clio webhooks auto-expire. The default is 3 days, the maximum is 31. If you register a webhook once and forget it, it goes quiet without an error and your integration silently stops reacting. Build a renewal job from day one.
The custom field trap: value-instance id vs field-definition id
This isn't strictly about uploads, but it's the next wall every Clio integrator hits, and it cost us real debugging time, so it earns a place here.
Clio custom fields are fully readable and writable over the API. You read them with GET matter?fields=custom_field_values{...} and you write them with a PATCH matter carrying a nested custom_field_values array. The number of fields is not a problem; firms run a hundred or two hundred custom fields and the API handles it fine.
The trap is the id. There are two ids in play and they are easy to confuse:
- The field-definition id (
custom_field: {id}), which identifies the field itself across the whole account. - The value-instance id, which identifies this matter's specific value for that field.
When you PATCH, the id inside custom_field_values must be the value-instance id, not the field-definition id. Grab the definition id (the obvious one) and your write silently misbehaves. The fix: read the matter's existing custom field values, pull the instance id off the value you want to change, and PATCH that.
Field type is the other constraint. Free-text fields normalize cleanly, which is why text is where AI-driven normalization actually shines. But picklist fields only accept predefined options (and each option caps at 55 characters), currency fields reject decimals, and date fields need a valid date. Before you promise to clean up every custom field on an account, inventory them by type. The plan for a free-text address field is not the plan for a currency field.
A note on data residency, if you're routing through an LLM
Plenty of teams building on Clio now want a language model in the loop: generate the draft, then push it in. If you're doing that for a Canadian firm, two facts are worth knowing before you wire anything up.
Clio has a Canadian region (ca.app.clio.com, aligned with PIPEDA). If a firm's account lives on the Canadian server, your integration has to call the Canadian base URL, not the default one. Confirm which server the account is on before you assume an endpoint.
The model side is harder. Anthropic, for example, has no Canadian region; data is processed in the US at rest. You can mitigate with the right surface (zero-data-retention is available on the API at the org level, not on the Team chat product, which is a common point of confusion) and a US inference pin, but cross-border processing is a fact you disclose plainly, not a thing you paper over. For regulated legal work this connects to professional-conduct duties: the Law Society of Ontario's 2024 guidance, conceptually aligned with ABA Formal Opinion 512, expects confidentiality, competence, supervision, and crucially a human verifying the AI's output. Whatever you build, leave a human-review gate before anything final lands in the file.
That's a deep topic on its own. The short version for an upload pipeline: get the file generated, route the sensitive content through a surface you can stand behind, and let a lawyer approve before the document is committed to the matter.
A quick checklist before you ship
- POST first, PUT to the
put_url, then PATCHfully_uploaded: true. All three, in order. - Don't send Clio auth headers on the S3 PUT. Match the content type you declared.
- Treat presigned URLs as short-lived. If the PUT is delayed, re-request from step one.
- Throttle to about 3 req/s, honor the
X-RateLimit-*headers, back off on 429, paginate at 200 per page. - No document webhook exists. Poll, or trigger on the
matterstage change. - Renew webhooks before they expire (3-day default, 31-day max).
- For custom fields, write the value-instance id, not the definition id, and respect field types.
None of this is exotic once you've seen it. But every one of these points is a place we watched an integration fail quietly, return a 200, and leave a developer staring at a matter that should have a document in it and doesn't.
Frequently Asked Questions
How do you upload a document to Clio via the API?
Three calls, not one. POST to /documents with the matter and filename metadata; Clio returns a presigned S3 put_url. PUT the raw file bytes to that URL (the request goes to S3, not Clio). Then PATCH the document with fully_uploaded: true to confirm the upload and make the document visible in Clio. Skip the third call and the document stays pending.
Why does Clio use a presigned S3 URL instead of a normal file POST?
Clio stores documents in Amazon S3 and lets your client upload bytes straight to S3 via a short-lived presigned URL. This keeps large files off Clio's API servers and off the rate limit (roughly 3 requests per second per app). The tradeoff: you manage three calls, and the S3 PUT is authenticated by the URL's own signature, not your Clio OAuth token.
Does Clio have a webhook for new documents?
No. The Clio Manage API has webhooks for activity, bill, calendar_entry, communication, contact, matter, and task, but not documents. Detect new documents by polling, or subscribe to the matter updated webhook and treat a stage change as the trigger, since a stage change is usually what generates the document. Clio webhooks also auto-expire (3 days default, 31 max), so renew them.
What's the most common mistake writing Clio custom fields via the API?
Using the field-definition id instead of the value-instance id. The id inside the custom_field_values array on a PATCH must identify this matter's specific value, not the field definition (custom_field: {id}). Read the existing value to get its instance id, then PATCH that. Field type matters too: free-text normalizes cleanly, picklists take only predefined options up to 55 characters, currency rejects decimals, and date needs a valid date.
Building on Clio?
We build Clio integrations and AI workflows for law firms, and we ship the connectors as open source so you can read the code instead of trusting a black box. Our Clio MCP connector handles the document upload flow described here, plus matters, contacts, time entries, tasks, notes, and an append-only audit log framed for ABA Opinion 512.
If you're wiring Clio into an AI drafting pipeline and want a second set of eyes on the architecture (residency, webhooks, the upload path, the human-review gate), we offer a free 30-minute technical review. No pitch deck, just an honest look at your design.
Book a free architecture review →
Or explore more from our legal tech practice: