Errors & rate limits
Error shape
Errors return a non-2xx status and a JSON body with a stable, machine-readable code and a human-readable message:
{ "code": "insufficient_scope", "message": "API key is missing required scope 'submissions.write'" }
Branch on code, not message. The message wording may change; the code is stable (we add new codes rather than renaming existing ones). Every error carries a code — a generic one derived from the status, plus specific codes that disambiguate cases sharing a status.
| Status | Meaning | Typical cause |
|---|---|---|
400 |
Bad request | Missing/invalid field (e.g. no tax_software, non-numeric tax_year), no documents uploaded |
401 |
Unauthorized | Missing, malformed, or unrecognized API key |
403 |
Forbidden | Key lacks the required scope; or a sandbox-only endpoint was called on a non-sandbox submission |
404 |
Not found | Unknown client/submission id (or not in your org) |
409 |
Conflict | Client already has an active submission; documents still uploading (call finalize first); or an Idempotency-Key conflict |
413 |
Payload too large | Direct multipart upload over 10 MB — use the presigned flow |
429 |
Too Many Requests | Rate limit exceeded — see below |
Error codes
The codes that disambiguate same-status conditions (handle these differently):
code |
Status | Meaning / what to do |
|---|---|---|
missing_authorization |
401 |
No Authorization header was provided. |
missing_api_key |
401 |
The Authorization header was present but carried no key. |
malformed_api_key |
401 |
The key isn't shaped like a mag_… key — usually truncated on copy. Re-copy the whole key. |
invalid_api_key |
401 |
Well-formed but unrecognized (revoked, or a typo that still parses). |
insufficient_scope |
403 |
The key isn't scoped for this endpoint. |
partner_paused |
403 |
The partner's API access is paused — new writes are blocked. Contact Magnetic. |
partner_unlinked |
403 |
The partner no longer manages this firm, so the key is inactive. Contact Magnetic. |
sandbox_only |
403 |
A sandbox-only control was called on something that isn't sandbox — a real submission, or (Partner API) a real firm. Only a firm's own admin can accept its management. |
no_input_documents |
400 |
The client has no finalized documents to submit — upload documents first. |
invalid_limit |
400 |
limit is not a positive integer, or is above the maximum — see Pagination. |
invalid_cursor |
400 |
cursor wasn't one we issued. Pass back next_cursor verbatim rather than constructing one. |
invalid_content_md5 |
400 |
The Content-MD5 sent with a direct upload doesn't match the bytes received — the upload was corrupted in transit; retry it. |
active_submission_exists |
409 |
The client already has an in-flight submission — wait for it, or cancel it. |
documents_still_uploading |
409 |
An upload isn't finalized — call finalize, or delete an abandoned one (list the client's documents, then DELETE the one in state uploading). |
document_attached |
409 |
The document is attached to a submission and can't be deleted. |
cancel_window_passed |
409 |
The 30-minute cancellation window has passed; the submission must run to completion. |
submission_already_terminal |
409 |
The submission is already completed/cancelled. |
invalid_idempotency_key |
400 |
The Idempotency-Key header was sent empty, or is longer than 255 characters. Any non-blank string within that limit is accepted — a UUID is the usual choice. Omitting the header entirely is fine; sending a blank one is not, so a key generator that returns nothing fails loudly instead of silently dropping the guarantee. |
firm_not_accepted |
409 |
(Partner API) The firm hasn't accepted your management yet, so it can't be acted on — chase its admin, or wait for acceptance. |
invitation_role_conflict |
409 |
(Partner API) The address has a pending invitation at a different role and it couldn't be withdrawn. Retry shortly. |
default_sandbox_protected |
409 |
(Partner API) Your default sandbox can't be deleted. |
firm_already_accepted |
409 |
(Partner API) The firm accepted your management, so it can no longer be deleted — it's theirs now. |
firm_revoked |
409 |
(Partner API) This firm's management was revoked, so it can't be accepted. Contact Magnetic if it should be re-linked. |
sandbox_limit_reached |
409 |
(Partner API) You already hold the maximum number of sandbox firms. Delete one to free a slot. |
idempotency_key_reuse |
409 |
The key was already used for a different request. Use a new key for a new request, or resend the original body to replay it. |
idempotency_request_in_progress |
409 |
A request with this key is still running — retry shortly for its result. Also returned, for the rest of the key's 24 hours, if we couldn't record a response after the work succeeded; there, reconcile with a GET rather than waiting. |
payload_too_large |
413 |
A direct multipart upload exceeded the 10 MB limit — use the presigned flow. |
rate_limited |
429 |
Back off and retry after Retry-After (see below). |
This table isn't exhaustive — other validation failures also carry a specific code (e.g. invalid_cursor, invalid_limit, invalid_content_md5 on a bad request). Generic per-status codes (bad_request 400, unauthorized 401, forbidden 403, not_found 404, conflict 409, payload_too_large 413, unprocessable_entity 422, internal_error 500) are used where no more specific code applies. In all cases, branch on code and treat an unrecognized one by its HTTP status.
Rate limits
Both write endpoints and the submission status reads (GET /v1/submissions/{id} and GET /v1/submissions) are rate limited per API key. We don't publish specific limits (they may change), so design for the 429 rather than a fixed number. When you exceed the limit you get a 429 with a Retry-After header (seconds) and a matching body field:
{ "code": "rate_limited", "message": "Rate limit exceeded", "retry_after_seconds": 42 }
Back off and retry after the delay — read it from either the Retry-After header or retry_after_seconds.
Webhooks are the recommended way to track a submission's status; polling
GET /v1/submissions/{id}is a supported fallback. Configure asubmission_updated_webhook_urlon your API key and we'll push a signedsubmission.updatedevent on every status change (see Webhooks) — no polling loop to rate-limit. If you do poll, poll a few times a day (not in a tight loop), and honorRetry-Afteron a429.
Idempotency & retries
- Don't double-submit: a client allows only one active submission (
409). Track the submission id you got back rather than re-creating. - Network errors on create are not automatically safe to retry — a timed-out
POSTmay still have succeeded server-side. If a create times out,GET /v1/clientsorGET /v1/submissionsto confirm whether the resource was created before sending another create request. - Download URLs (
download_url) are presigned and expire — re-fetch them from the submission instead of caching long-term.
Cancellation codes
When a submission's status is cancelled, the submission carries a human-readable cancellation_reason and a stable, machine-readable cancellation_code you can branch on. The codes are stable — we add new ones rather than renaming existing ones.
cancellation_code |
Meaning |
|---|---|
encrypted_file |
A submitted file was password-protected/encrypted. Provide an unlocked file and resubmit. |
unsupported_file |
A submitted file type is not supported. Convert to a supported type and resubmit. |
empty_file |
A submitted file was empty (0 bytes). |
outdated_tax_software_file |
A tax-software file was from a prior tax year. Provide a current-year file. |
unextractable_zip |
A submitted ZIP could not be extracted (corrupt/unsupported archive). |
mismatched_client |
Uploaded documents didn't match the client on the submission. |
proconnect_link_failed |
The linked ProConnect return could not be opened. |
unsupported_return_type |
The return type isn't currently supported (e.g. Form 1041 estates/trusts). |
cancelled_by_firm |
Cancelled by a member of the firm. |
client_deleted |
The client was deleted, so its in-flight submission was cancelled automatically. Nobody had started work on it — see deleting a client. |
other |
Cancelled for a reason without a more specific code. |
Some cancellations are the taxpayer's/firm's to resolve (e.g. encrypted_file, mismatched_client); as an integrator you'll typically forward those back to the firm. Because you talk to us on the firm's behalf, decide per code whether to surface it to the firm or handle it yourself.