Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
TL;DR
Customer: A US digital marketing agency, running client campaign delivery across paid media, creative and analytics teams.
The Move: ClickUp to Notion. Tasks, comments, custom fields, attachments, documents and the relations between them, across a selected set of workspaces. Not a helpdesk migration — this was the system the agency ran its client work in.
The Scope: Roughly 227,000 tasks pulled, close to 129,000 migrated in the agreed scope, over 46,000 comments posted, more than 4,700 document pages, and tens of thousands of attachments. Four years of delivery history.
The Roadblock: Notion's API returns 404 for a page that exists but is not shared with you — identical to the response for a page that has been deleted. On a workspace being actively reorganised, that makes "gone" and "invisible to us" indistinguishable from the response alone, and whole areas can go dark between runs while every record in them sits there intact.
The Outcome: A Notion workspace holding the agency's delivery history with relations, comments, attachments and statuses intact — every figure independently reproduced by the client before sign-off.
By the Numbers
- Tasks pulled from source: roughly 227,000
- Tasks migrated: close to 129,000
- Comments posted: over 46,000
- Live tasks written under a human-edit guard: around 8,400
- Peak throughput: around 7,900 tasks an hour
- Custom fields mapped: over 5,400
- Document pages migrated: more than 4,700
- Items raised for review by independent verification: a little over 1% of the largest wave, all accounted for
- Data span: four years of delivery history
The Challenge: Moving the System the Business Runs In
Most migrations move a record of work that has already happened. This one moved the place the work happens. The agency ran client campaign delivery in ClickUp — tasks, briefs, approvals, creative assets, the relations between a campaign and its deliverables — and every one of those objects had someone depending on it that week.
That changed what "done" meant. A status that is merely historical can be approximate. A status on a live task is an instruction to somebody, and it has to be the same instruction the day after the move as the day before.
Three things shaped how it was run:
- Every figure had to be independently reproducible. The client's reviewer re-derived each number we published, read-only, from outside our tooling, for a week. The reporting was built for exactly that — every figure produced from its own artefact, reproducible, with any difference shown as a diff — which is why the final numbers hold up to someone else's arithmetic.
- Notion's rate limit is not the performance story; utilisation is. The destination allows a fixed number of requests per second, and the work that gets a migration near that ceiling is all on the caller's side — keeping a call ready rather than waiting on downloads and database reads.
- The workspace was live and evolving. Content was being organised into new teamspaces while the migration ran, so what the integration could reach changed between runs — something to design for rather than freeze the business around.
When a 404 Does Not Mean Deleted
Notion answers a request for a page that exists but has not been shared with your integration exactly as it answers a request for a page that has been deleted: a 404, with nothing in the response to separate the two.
On a workspace being actively reorganised, that ambiguity is not theoretical. Content moves into new teamspaces, integration access does not follow it, and an entire area can go dark between one run and the next while every record in it is perfectly intact. Areas as large as 190 databases, and as large as close to 5,800 records, can go dark this way between one run and the next. Every record stays intact in its new location, and a re-share brings the whole area back.
Three rules apply to every Notion project we run. A 404 is recorded as not visible, never as deleted — in reports, in logs, and in the names of the variables themselves. A wall of them with a clean boundary is read as the signature of an access change rather than data loss. And any check meant to prove that content is still alive has to run inside the same access scope as the content itself, or it proves only that the check works. Where a workspace is being reorganised, landing locations are resolved at write time from the current parent chain.
The ClonePartner Solution: Waves, Verification, and a Guarded Live Write
Migration ran in waves, each one a scoped set of spaces, so throughput and quality were measured on real data before the next wave committed more of it.
Not everything pulled was in scope. Roughly 227,000 tasks were pulled from ClickUp; close to 129,000 sat inside the spaces the agency chose to move. The gap is scope. The pull was deliberately wider than the migration so that scoping could be decided space by space against the real contents of each one, on the actual data rather than an estimate of it.
Peak throughput reached close to 7,900 tasks an hour, and almost none of that came from the rate limit. The ceiling never moved. What moved was utilisation: enough workers that the queue always has a call ready, no block-listing read on a page that is new and cannot have blocks, attachment uploads bounded in parallel inside each task rather than serialised, a poll interval that backs off while a large upload settles, and one warm queue across the whole multi-step chain rather than a cold start at every step. The largest wave peaked near 7,900 tasks an hour and moved close to 43,000 tasks in about six hours. Most of what makes a Notion import slow is on the caller's side of the connection, not the platform's.
Verification was built to be independent of the push. It re-derives what each record should look like from the raw source rather than trusting the mapping the push used, so the two have to agree on their own rather than confirming each other. It stamps what it has verified, so a later pass only re-checks what has changed since, in seconds rather than a full re-scan. On the largest wave it raised a little over 1% of roughly 44,000 items for review, and every one was classified and accounted for before closeout.
Idempotency is a design property, not a hope. Every writer marks what it has finished, so a re-run is a no-op scan. The window between a page being created and that fact being recorded is covered by a reclaim sweep, which adopts the destination page by its source ID instead of creating a second one. Repeating any step is free by design, and across the whole programme the chain produced no duplicate records at all.
Writing Into a Workspace People Were Using That Day
Setting the final status on around 8,400 live tasks meant writing into a workspace the agency's own people were editing that day — a write most migrations will not attempt once the destination is in use.
Four conditions were set by the client before any write, and all four were met: an identifiable run stamp on every change, human-edited rows surfaced rather than overwritten, a per-database pre-flight check, and the completion date re-asserted and verified on every row written.
The human-edit guard is the interesting part. Notion's page-level last-editor field records only the most recent writer, so no single scan taken during a migration can show who had touched a page before it. The guard therefore unions a fresh snapshot of the target databases with the baseline taken earlier — close to 4,800 pages in total. That scan found around forty rows last edited by a real person rather than by the migration. A handful were in the repair plan, all of them already reading the correct status and therefore skipped, and zero were in the write set. The guard was load-bearing rather than decorative, and the plan went to the client unchanged.
The dry run reconciled to the row: every write decomposing exactly into the client's own ruled scope plus a small number of deliberate reversions, with zero writes to any row outside that scope.
The Results: Closed Out by the Client
The waves completed in sequence, with the status repair running to plan after the client gave an explicit go — having re-derived it themselves first.
What the agency has in Notion is close to 129,000 tasks with their comments, attachments, custom fields and cross-database relations intact, alongside more than 4,700 migrated document pages. The relations matter more than the count: a campaign that pointed at its deliverables in ClickUp points at them in Notion, which is the difference between a migrated archive and a system people can work in.
The client closed both workstreams themselves, each one after re-deriving the result.
What Made This Migration Different
For teams planning a ClickUp to Notion move, three details from this project are worth noting:
-
A 404 from Notion is not evidence of deletion. It is the same response for a page that was never shared with your integration. If a whole space goes dark at once with a clean boundary, that is an access change, not data loss — and your liveness control has to run inside the same access scope as the thing you are testing, or it proves nothing at all. Read it as deletion and you will report a loss that never happened.
-
Rate limits are rarely the bottleneck. Utilisation is. Peak throughput here reached close to 7,900 tasks an hour against a ceiling that never changed. It came from keeping a worker's call always ready — enough workers, no read a page cannot need, parallel uploads within a task, and one warm queue instead of a cold one per step. Before asking a vendor to raise a limit, measure whether you are using the one you have.
-
Verification has to be independent of the thing it verifies. Checking a migration using the same mapping the migration used will confirm whatever the migration believed. Re-deriving expectations from the raw source — ideally from a full Notion export or the untouched source records — is the only check that can disagree with the migration at all. And when a client re-derives your numbers themselves, the right response is to hand them the diff — a figure that survives someone else's arithmetic is worth more than one nobody ever checked.
Frequently Asked Questions
- Does a 404 from the Notion API mean a page has been deleted?
- No. Notion returns 404 for a page that exists but is not shared with your integration, which is identical to the response for a page that has been deleted. On a workspace being actively reorganised that makes "gone" and "invisible to us" indistinguishable from the response alone. On this project whole areas went dark between runs while every record in them sat there intact, and a re-share brought all of it back.
- How do you build a liveness check that can tell a deletion from an access change?
- The control has to run inside the same access scope as the thing being tested. A control that runs in a different space you still have access to can only prove that the probe works; it can never distinguish a deleted page from an invisible one. Mass 404s with a clean boundary are the signature of an access change, not a deletion.
- What limits throughput on a ClickUp to Notion migration?
- Rarely the rate limit, and usually utilisation. The largest wave here sustained close to 7,900 tasks an hour against a ceiling that never moved, by keeping enough workers that the queue always has a call ready, skipping a block-listing read on a page too new to have blocks, bounding attachment uploads in parallel inside each task, and carrying one warm queue across the whole chain instead of a cold start at every step.
- Can a migration run while people are still working in the destination workspace?
- Yes, with guards. Landing locations are resolved at write time from the current parent chain, and any bulk write needs an identifiable run stamp, a per-database pre-flight check, and a human-edit scan that surfaces rows a real person has touched rather than overwriting them.
- Do ClickUp relations between tasks survive a move to Notion?
- Yes, and they matter more than the task count. A campaign that pointed at its deliverables in ClickUp points at them in Notion, along with comments, attachments, custom fields and statuses. That is the difference between a migrated archive and a system people can actually work in.