Limits
What the API enforces, and what happens when you reach it.
Free and paid
| Free | Paid | |
|---|---|---|
| Projects | 1 | 1 per slot, plus one free project |
| Assets per project | 50 | No published limit |
| Members per project | 2 | No published limit |
| Storage | 250 MB | 2 GB per project slot, pooled |
| Max file size | 10 MB | 25 MB |
| API requests | 50,000 / month | Unmetered |
“No published limit” means exactly that: there is no cap you will meet in normal use. A high backstop exists to stop runaway automation, and you will never see it unless something is wrong.
How storage is counted
Paid storage is pooled across your account. Each project slot grants 2 GB, and every paid project draws from the combined total. Three slots is one 6 GB pool, not three separate 2 GB allowances. A free project keeps its own allowance. The bonus free project that comes with a paid slot has its own 250 MB and is excluded from the account pool entirely, so filling it does not touch your paid storage, and vice versa. It follows the free 5 MB file limit too, whoever owns it. You are metered on what is stored, not what you upload. Images are rebuilt before they are measured, and stripping metadata usually makes the stored file a little smaller than the one you sent. The format does not change, so a PNG is stored as a PNG. Documents are stored exactly as uploaded. The file-size limit applies to the stored result. Delivery is never blocked by storage. Passing your quota stops new uploads. Content already published keeps serving.
Per asset
| Text length | Up to 5,000 characters. Configurable per asset between 5 and 5,000. |
| HTML length | 100,000 characters, roughly 100 KB, and about three times a 5,000-word article, so long-form content belongs in an HTML asset. You can declare a lower ceiling per asset to stop an editor pasting something enormous. |
| JSON length | 50,000 characters |
| Version history: images, documents | 10 versions retained |
| Version history: text, HTML, JSON | 50 versions retained |
Older versions are trimmed automatically once the limit is passed. Images retain fewer because each version keeps its own stored files.
Image variants
Every image asset returns an object of URLs. The sizes are maximum widths. Aspect ratio is always preserved, so the height follows your source image. A variant that exists is exactly the width below, so you can write these numbers into a srcset without measuring anything. The original is the exception, since it is whatever you uploaded; its dimensions come back under meta.
| Variant | Max width |
|---|---|
| original | As uploaded, after conversion |
| large | 1200 px |
| medium | 800 px |
| thumb | 400 px |
Not every variant always exists.
Variants are never upscaled, so a 500 px upload has no large. GIF and SVG return original only, to keep animation and vector data intact. Fall back to original when the variant you want is absent.
In code this shows up as undefined, not an error. A page that reads image.thumb works against a large JPEG and renders a broken image the first time a client uploads a GIF, so read it as image.thumb ?? image.original.
Rate limits
Exceeding a limit returns 429 with a message naming which one you hit. Rate-limited requests are not counted against your quota.
The monthly request limit applies to free projects only. Paid slots are unmetered, with no monthly ceiling and no overage charge. If you see Monthly API request limit reached for this project on a paid slot, that is a bug worth reporting.
Caching and freshness
Publishing takes effect immediately. The cache is invalidated on publish, so the next request returns the new content. You never wait out a timer to see a change go live. Otherwise responses are cached for 5 minutes. That is the ceiling on how stale a response can be when nothing has been published. Check X-Duggie-App-Cacheon the response to see whether you were served from cache. Preview-token requests bypass the cache entirely, so a draft is always current.Cached responses still count against your monthly requests. The cache protects response times, not your allowance. If you are fetching per page view on a free project, cache on your own side too, since 50,000 a month is roughly 1,600 views a day.
How to cache on your side, and what your site sees if the API is slow or down, is on Caching and reliability.
Service status
Current status, past incidents and planned maintenance are on status.duggiecms.com. It is hosted separately from the API, so it stays reachable during an outage of ours. The System Status badge in the dashboard footer reports the content endpoint's own health check and links to the same page.
What the API does and doesn't do
The public API has two endpoints: read your content, and write it in bulk. If you think we should support others, please submit a features request through the contacts page.
Publish webhooks are for static builds
Duggie assumes you fetch content at request time, and a site that does needs no notification: the next request sees the new content. If you build statically (Next.js SSG, Astro, Hugo), a paid project can call one endpoint whenever published content changes, which is how a build hook gets triggered. See Webhooks.
Batch writes
| Assets per request | 1 to 100 |
| Tag length | Up to 200 characters |
| Types accepted | Text, JSON, HTML. Images are uploaded separately. |
One item with bad content does not fail the batch. Content that breaks a rule is skipped and the rest are applied: invalid JSON, HTML or text over its length limit, or a value that breaks one of the asset's own field rules. The response is 200with a per-item result, so check the body rather than the status code to know what landed.A malformed request is rejected whole. An item missing its tagorcontentis a bad request rather than a bad row, so the call answers400and nothing is written. Fix the payload and send it again.Creation stops at your asset limit. A batch that would take you past it creates what fits and skips the rest, rather than rejecting the whole call. Updates to existing assets are unaffected. Asset type is fixed at creation. Sending a different typefor an asset that already exists does not change it; the existing type wins.