DraftFilterDeveloper API

Developer reference

Rate Limits

The public v1 API protects queue capacity, worker capacity, and input size. Size limits are part of the OpenAPI schema and are enforced before jobs are accepted.

Score limits:

text
up to 100 items per job
up to 20,000 characters per item
up to 100,000 total characters per job

Rewrite limits (batch parity with score jobs):

text
up to 100 items per job
up to 5,000 characters per item
up to 100,000 total characters per job

Pagination limits:

text
limit defaults to 50
limit must be between 1 and 100

File upload scan limits:

text
PDF, DOCX, or TXT only
10 MB maximum file size
Pro plan required

Monthly plan allowances:

text
Plan   Scan words/month   Humanize words/month   Max characters/scan
Free   10,000             0                      15,000
Plus   200,000            30,000                 50,000
Pro    1,000,000          150,000                50,000
Max    20,000,000         3,000,000              50,000

Free accounts include 10,000 scan words per month. Humanize usage is metered at 3x input words. For example, a 1,000-word rewrite draws 3,000 words from the humanize pool.

Anonymous browser scans are limited to 5 per day. Anonymous scans are limited to 5,000 characters. Anonymous browser credentials can score and run one synchronous rewrite preview per day (POST /v1/rewrites returns a redacted teaser: before/after scores plus the single most-improved sentence, never the full rewritten text). The rewrite preview shares the 5,000-character anonymous cap. A second anonymous rewrite in the same day returns 429 with error.code set to anonymous_rewrite_quota_exceeded. Async rewrite jobs require a signed-in user or an API key and return 403 with error.code set to anonymous_rewrite_jobs_forbidden on an App Check-only request. Free accounts get 402 with error.code set to upgrade_required when creating sync or async rewrites. Anonymous allowances are keyed on the App Check app and network origin and reset at UTC midnight. The sixth anonymous scan of the day returns 429 with error.code set to anonymous_quota_exceeded; an over-size anonymous scan or rewrite preview returns 429 with error.code set to anonymous_input_too_large.

Humanizer access requires a paid signed-in plan or an API key on a paid account. File upload scanning requires a Pro signed-in account or an API key whose account is on Pro. Non-Pro upload requests return 402 upgrade_required; anonymous App Check upload requests return 403 insufficient_scope.

Signed-in web requests and API-key requests share the same account-level plan pools. Free accounts use calendar-month periods. Paid accounts use the current Stripe subscription period, so a successful subscription webhook period update naturally starts a fresh allowance document. A score, scan, uploaded file scan, rewrite, or rewrite job that would exceed its pool returns 402 with error.code set to allowance_exhausted, error.pool set to scan or humanize, and error.docs pointing back to this page.

Operational request-rate and queue-concurrency limits may be adjusted to protect GPU capacity. Clients should use idempotency keys on create requests, poll with backoff, and retry only retryable failures. The current public OpenAPI contract does not publish a fixed numeric requests-per-minute quota.

Shared scan result reads (GET /v1/shared/{shareId}) are unauthenticated capability reads. They do not consume plan word pools and do not currently have a separate published numeric quota. Unknown and revoked share IDs return the same 404 response.

When an operational limit is exceeded, create requests return 429 with error.code set to rate_limited. Requests denied by billing policy return 402 with error.code set to billing_disabled, upgrade_required, or allowance_exhausted. These failures happen before job creation, artifact writes, callback token generation, or task dispatch.

Usage API

Read your own scan/humanize word usage, allowances, and period totals programmatically with GET /v1/usage (scope usage:read; the same endpoint backs the dashboard usage page):

json
{
  "accountId": "acct_example",
  "plan": "plus",
  "pools": {
    "scan": {
      "usedWords": 800,
      "allowanceWords": 200000,
      "remainingWords": 199200,
      "periodStart": "2026-07-01T00:00:00Z",
      "periodEnd": "2026-08-01T00:00:00Z"
    },
    "humanize": {
      "usedWords": 600,
      "allowanceWords": 30000,
      "remainingWords": 29400,
      "periodStart": "2026-07-01T00:00:00Z",
      "periodEnd": "2026-08-01T00:00:00Z"
    }
  },
  "daily": [
    {"date": "2026-07-01", "scanWords": 700, "humanizeWords": 0},
    {"date": "2026-07-02", "scanWords": 100, "humanizeWords": 600}
  ],
  "perKey": [
    {"apiKeyId": null, "label": "Web", "scanWords": 100, "humanizeWords": 0},
    {"apiKeyId": "key_alpha", "label": "key_alpha", "scanWords": 700, "humanizeWords": 600}
  ]
}

perKey groups signed-in web and anonymous browser usage under the Web row with a null apiKeyId.