# Red Eagle CDR API — Complete Integration Reference > Everything an integrator (human or AI) needs to use the Red Eagle Content Disarm and Reconstruction API: authentication, every endpoint, the document envelope, the error catalogue and the operational contract. This file is maintained by hand against the live v1 contract; where they ever disagree, the machine-readable source of truth is https://api.cdr.redeagle.tech/v1/openapi.json. Base URL: `https://api.cdr.redeagle.tech`. All error responses are RFC 9457 `application/problem+json` with a stable machine-readable `code` extension. All timestamps are ISO 8601 with offsets. ## Authentication OAuth2 client credentials. Request a token from `https://identity.redeagle.tech/connect/token`: ``` POST /connect/token Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&client_id=&client_secret=&scope=cdr.api ``` Send it as `Authorization: Bearer ` on every API call. Tokens are valid for one hour — cache and reuse until close to expiry; never request a token per call. Credentials are created, rotated and revoked in the account portal (https://account.redeagle.tech). Requests are attributed to your organisation: you can only see your own documents (lookups of other organisations' ids return 404). ### API keys (integration platforms) Where an OAuth token exchange is impractical — workflow platforms such as Zapier, Power Automate, n8n or Make, or a quick test — create an API key in the account portal and send it on every call instead: ``` X-Api-Key: recdr_live_<43 characters> ``` `Authorization: Bearer ` is also accepted (the `recdr_` prefix tells the API it is a key, not a token). A key grants the same document-API access as a bearer token, is attributed to the same organisation, meters against the same plan and honours the same rate limits. Account administration always requires OAuth — a key can never create or revoke keys. Keys do not expire. Rotation is: create a second key, repoint the integration, revoke the old one — revocation takes effect within a minute, and revoked keys stay visible in the portal for reference. Create one key per platform so each can be revoked on its own. The key is shown exactly once, at creation; the service stores only a hash. OAuth client-credentials authentication remains the recommended default for server integrations. ## Submit a document (multipart, files up to 100 MB) ``` POST /v1/documents Content-Type: multipart/form-data Idempotency-Key: form field "file": the document optional form field "deleteAfterDownload": true|false (default false) ``` Returns `202 Accepted` with the document envelope (see below) — `id` is the handle, `statusUrl` is where to poll. ### Synchronous mode Add `?wait=true` (or the header `Prefer: respond-sync`). If the document reaches a terminal state within the wait budget, the response is `200 OK` with the final envelope — small documents typically complete in under 3 seconds. If not, you get the normal `202` and continue by polling; nothing is lost. Files routed to the large-file lane skip the wait and return `202` immediately. ## Submit a large file (over 100 MB, up to your plan's ceiling) Three calls: ``` # 1. Create an upload (returns uploadId, uploadUrl, maxSizeBytes, expiresAt) curl -X POST https://api.cdr.redeagle.tech/v1/uploads \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -H "Idempotency-Key: upload-$MY_REF" \ -d '{"fileName": "big-report.pdf"}' # 2. PUT the bytes straight to storage (no Authorization header; the URL is pre-signed). # Optionally send Content-MD5 — storage then verifies the bytes arrived intact. curl -T big-report.pdf "$UPLOAD_URL" -H "x-ms-blob-type: BlockBlob" # 3. Submit the upload by reference (same envelope and billing as multipart) curl -X POST https://api.cdr.redeagle.tech/v1/documents \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -H "Idempotency-Key: submit-$MY_REF" \ -d '{"uploadId": "", "deleteAfterDownload": false}' ``` The `uploadUrl` is valid for 60 minutes; the upload itself stays submittable for 24 hours from creation and is one-shot (submitting it twice returns `409 UploadAlreadySubmitted`). An upload that exceeds your plan's ceiling is refused at submission with `413 FileTooLargeForPlan` (the body carries `maxSizeBytes`). ## Check status ``` GET /v1/documents/{id} ``` `200 OK` with the envelope: | Field | Meaning | | --- | --- | | `id` | The document id | | `status` | `pending`, `processing`, `completed`, `rejected` or `failed` | | `outcome` | On completion: `cleaned` (the file was rebuilt and anything disallowed removed) | | `modifications` | On completion: what was done, e.g. `["rebuilt"]` | | `reason` | On `rejected`/`failed`: the stable taxonomy code (below) | | `threatNames` | On threat rejections: names of what was found | | `submittedAt` / `completedAt` | Timestamps | | `downloadUrl` / `downloadExpiresAt` | Pre-signed download link for the cleaned file + its expiry (absent with delete-after-download — use the download route) | | `statusUrl` | Absolute URL of this resource — poll this | | `pollAfterSeconds` | How long to wait before the next poll | | `deleteAfterDownload` / `deletedAt` | Early-deletion flag and, once deleted, when | Status reads have their own generous rate-limit partition (10x your plan's limit) so polling never starves your submissions. ### Outcome taxonomy (the `reason` / `outcome` codes) Rejections — the file's own properties stopped it; **billed** (the product doing its job), final: - `threat_detected` — active threats that cannot be removed; `threatNames` lists them - `password_protected` — the file cannot be opened for reconstruction - `invalid_file` — corrupt, or not a valid file of its declared type - `unsupported_file_type` — not a supported document type - `policy_violation` — content the security policy does not permit - `file_too_large` — the rebuilt file exceeds your plan's ceiling. Normally refused at submission, but archives are only measured once expanded, so a small ZIP holding large contents reaches this state after processing Failures — the service's fault; **auto-credited**: - `timeout` — processing did not finish inside the time budget - `internal_error` — a fault inside the service ## Download the cleaned file Either follow the envelope's `downloadUrl` directly (no Authorization header — it is pre-signed and time-limited; storage rejects a signed link accompanied by one with `400 InvalidAuthenticationInfo`), or: ``` GET /v1/documents/{id}/download ``` `302` redirect to the current pre-signed link while it is valid; `404` after expiry; `409` with the envelope while the document is not completed; `410 DocumentDeleted` after deletion. Clients built for the redirect endpoint drop the `Authorization` header automatically when they follow the cross-origin `302`. An `X-Api-Key` header is different: storage simply ignores it, so an integration platform that attaches the key to every request — including the redirect follow — still downloads cleanly. This is one of the reasons `X-Api-Key` is the canonical way to send a key. ## Delete early / retention Cleaned files are retained for **24 hours** (the envelope's `downloadExpiresAt`), then deleted. Submitted content is deleted as soon as processing completes. To shorten the window: - `DELETE /v1/documents/{id}` — idempotent `204`; refuses in-flight documents (`409 DocumentNotDeletable`); status stays readable afterwards - `deleteAfterDownload: true` at submission — the cleaned file is removed shortly after your first download begins (a grace window covers retries) The service is a processing pipeline, not a storage service: keep your own originals and collect cleaned files promptly. ## Account ``` GET /v1/account ``` Fresh entitlement snapshot: plan, period allowance and usage, credit balance, rate limit, `maxFileSizeBytes` (multipart lane) and `maxUploadSizeBytes` (your upload-lane ceiling). ## Usage headers Submission responses carry live usage (also on refusals where meaningful): - Tier plans: `X-CDR-Usage-Used`, `X-CDR-Usage-Allowance`, `X-CDR-Usage-Remaining` - Pay-as-you-go: `X-CDR-Credit-Balance-Pence` (post-charge) ## Idempotency Send `Idempotency-Key` (your own stable string, up to 200 characters) on `POST /v1/documents` and `POST /v1/uploads`. For 24 hours, a retry with the same key replays the original response byte-for-byte with `Idempotency-Replayed: true` — safe retries by construction. A concurrent duplicate gets `409 IdempotencyKeyInFlight` (retry shortly); reusing a key across different endpoints gets `422 IdempotencyKeyEndpointMismatch`. ## Error catalogue Every non-2xx body is problem+json: `type` links the catalogue entry, `code` is the stable machine-readable value, `detail` the human message. Branch on `code`. | Code | HTTP | Meaning | What to do | | --- | --- | --- | --- | | `ValidationFailed` | 400 | A request value is missing/invalid; see `errors` | Fix the request | | `NoFileProvided` | 400 | No `file` form field, or it is empty | Send the file | | `UnsupportedFileType` | 400 | Extension is not a supported type | Check supported formats | | `FileTooLargeForPlan` | 413 | Over a lane/plan ceiling; body carries `maxSizeBytes` and, when the upload lane would accept it, `acceptedByLane` | Use the upload flow, or upgrade | | `UploadNotFound` | 404 | Unknown, expired or another organisation's `uploadId` | Create a fresh upload | | `UploadAlreadySubmitted` | 409 | Uploads are one-shot | Use the document id you already have | | `IdempotencyKeyInFlight` | 409 | The first request with this key is still running | Retry shortly | | `IdempotencyKeyEndpointMismatch` | 422 | Key reused across endpoints | Use per-endpoint keys | | `InsufficientCredit` | 402 | PAYG balance cannot cover the per-document price; body carries the balance and price | Top up in the portal | | `CustomerSuspended` | 403 | The account is suspended | Contact support / check billing | | `NotProvisioned` | 403 | The organisation has no active plan | Complete signup in the portal | | `QuotaExceeded` | 429 | Monthly allowance used up; `Retry-After` gives the period end | Wait, or upgrade | | `RateLimited` | 429 | Per-minute limit reached; `Retry-After` in seconds | Back off and retry | | `DocumentNotDeletable` | 409 | Delete refused while in flight | Wait for a terminal state | | `DocumentDeleted` | 410 | The cleaned file was deleted | Status remains readable | ## Rate limits Per plan, per minute: Pay-as-you-go 30 · Starter 60 · Growth 120 · Business 180 (Volume: custom). Status reads (`GET /v1/documents/*`) run on a separate partition at 10x your limit. Exceeding a limit returns `429 RateLimited` with `Retry-After`. ## File types and size ceilings Supported: Word (DOC, DOT, DOCX, DOCM, DOTX, DOTM), Excel (XLS, XLSX, XLSB, XLSM, XLTX, XLTM, XLAM), PowerPoint (PPT, PPS, POT, PPA, PPTX, PPTM, POTX, POTM, PPAM, PPSX, PPSM), PDF, FDF, RTF, OpenDocument (ODT, ODS, ODP), SVG, HTML, HTM, images (JPG, JPEG, PNG, GIF, BMP, TIF, TIFF, WEBP) and archives (ZIP, GZ). ### Formats returned in a different format Some documents cannot be rebuilt in their original format, so they are converted rather than refused — a viewable PDF beats a file you cannot open at all. When this happens the completed envelope carries `"converted"` in `modifications` and an `outputFormat` field, and the download is named with the new extension. **Do not assume the file you get back has the extension you sent.** | Submitted | Returned as | |---|---| | SVG, HTML, HTM | PDF | | ODS, ODP | PDF | | GZ | ZIP | Everything else is returned in the format it was submitted in. ODT, RTF, WEBP and ZIP are rebuilt in place. ### Archives A ZIP is accepted only if **every file inside it** is itself a supported type; one unsupported member rejects the whole archive with `unsupported_file_type`, and one infected member rejects it with `threat_detected`. A clean archive comes back as one rebuilt archive. ### Modifications `modifications` on a completed document is `["rebuilt"]` — every accepted file is disarmed and reconstructed — plus `"converted"` when the document is returned in a different format from the one submitted. Ceilings: the multipart lane accepts files up to **100 MB** on every plan. Larger files go through the upload flow, up to your plan's ceiling: **250 MB** on Pay-as-you-go and Starter, **450 MB** on Growth and above. `GET /v1/account` reports your effective values. **Size limits apply to the uncompressed contents.** An archive that is small on disk may still exceed the limit once expanded — a 20 MB ZIP holding 600 MB of documents is refused with `file_too_large` after processing, not at upload. ## Support support@redeagle.tech — include the document id, the timestamp and the `code` from any error body.