{"components":{"schemas":{"Client":{"properties":{"created_at":{"format":"date-time","type":"string"},"friendly_name":{"type":"string"},"id":{"example":42,"type":"integer"},"taxpayer_tin":{"nullable":true,"type":"integer"}},"type":"object"},"ClientDetail":{"allOf":[{"$ref":"#/components/schemas/Client"},{"properties":{"submissions":{"items":{"$ref":"#/components/schemas/SubmissionSummary"},"type":"array"}},"type":"object"}]},"ClientList":{"properties":{"clients":{"items":{"$ref":"#/components/schemas/Client"},"type":"array"},"next_cursor":{"description":"Pass as ?cursor= to fetch the next page. null on the last page.","nullable":true,"type":"string"}},"type":"object"},"CreateClientRequest":{"properties":{"friendly_name":{"example":"Jane Q. Taxpayer","type":"string"},"taxpayer_tin":{"nullable":true,"type":"integer"}},"required":["friendly_name"],"type":"object"},"CreateSubmissionRequest":{"properties":{"proconnect_client_id":{"description":"Required when tax_software is ProConnect: the numeric clientId from the ProConnect return URL.","nullable":true,"pattern":"^\\d{12,20}$","type":"string"},"proconnect_engagement_id":{"description":"Required when tax_software is ProConnect: the engagementId UUID from the ProConnect return URL.","nullable":true,"pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"tax_software":{"description":"Which tax software to prepare in. Case-sensitive; must be one of the enum values.","enum":["UltraTax","ProConnect","Drake","Axcess","Lacerte"],"example":"Drake","type":"string"},"tax_year":{"example":2025,"type":"integer"}},"required":["tax_software","tax_year"],"type":"object"},"DeleteResult":{"properties":{"deleted":{"type":"boolean"},"id":{"type":"integer"}},"type":"object"},"Document":{"properties":{"category":{"description":"For output documents only: 'tax_return' (the tax-software return file, e.g. a Drake .DI5 / UltraTax .csd), 'workpapers' (the workpapers PDF), or 'review_notes' (the reviewer's notes, delivered as a .txt and a rendered PDF). Absent on input documents and internal-only outputs.","enum":["tax_return","workpapers","review_notes"],"nullable":true,"type":"string"},"download_url":{"nullable":true,"type":"string"},"filename":{"type":"string"},"id":{"example":7,"type":"integer"},"state":{"description":"Lifecycle state of the document: 'uploading' (presigned, not yet confirmed), 'uploaded' (available for the next submission), or 'submitted' (attached to a submission).","enum":["uploading","uploaded","submitted"],"type":"string"}},"type":"object"},"DocumentList":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/Document"},"type":"array"},"failed_document_ids":{"description":"Ids of documents that failed. Preferred over failed_document_indices. On the direct multipart upload this is always empty \u2014 a failed direct upload never created a row, so it has no id (use failed_document_indices there).","items":{"type":"integer"},"type":"array"},"failed_document_indices":{"deprecated":true,"description":"DEPRECATED (removed in 1.1.0) \u2014 prefer failed_document_ids. Positions into the request array; retained because a failure with no created row (direct upload) has no id.","items":{"type":"integer"},"type":"array"}},"type":"object"},"Error":{"properties":{"code":{"description":"Stable, machine-readable error code \u2014 branch on this rather than string-matching `message`. Generic per-status codes plus specific ones that disambiguate same-status cases (e.g. `active_submission_exists` / `documents_still_uploading` are both 409). This is the full set across the whole API; a given endpoint only returns the subset that applies to it \u2014 see each endpoint's error responses and the Errors & limits guide.","enum":["bad_request","unauthorized","forbidden","not_found","conflict","payload_too_large","unprocessable_entity","rate_limited","internal_error","missing_authorization","missing_api_key","malformed_api_key","invalid_api_key","insufficient_scope","partner_paused","partner_unlinked","sandbox_only","no_input_documents","invalid_cursor","invalid_limit","invalid_content_md5","invalid_idempotency_key","active_submission_exists","documents_still_uploading","document_attached","cancel_window_passed","submission_already_terminal","firm_not_accepted","invitation_role_conflict","firm_already_accepted","firm_revoked","default_sandbox_protected","sandbox_limit_reached","idempotency_key_reuse","idempotency_request_in_progress"],"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"FinalizeDocumentsRequest":{"properties":{"document_ids":{"example":[7,8],"items":{"type":"integer"},"type":"array"}},"required":["document_ids"],"type":"object"},"Organization":{"properties":{"default_tax_software":{"nullable":true,"type":"string"},"organization_id":{"type":"string"},"owner":{"description":"Who this key acts as: the partner's name for a partner-issued key, or the firm's own name for a firm-direct key.","nullable":true,"type":"string"},"sandbox":{"description":"True when this is a sandbox key (its submissions never reach a real preparer).","type":"boolean"},"scopes":{"items":{"enum":["clients.read","clients.write","documents.write","submissions.read","submissions.write"],"type":"string"},"type":"array"}},"type":"object"},"PrepareDocumentsRequest":{"properties":{"documents":{"items":{"properties":{"content_md5":{"description":"Optional base64-encoded 128-bit MD5 of the file. When set, it's pinned into the presigned upload so S3 rejects a corrupted upload (integrity check).","nullable":true,"type":"string"},"content_type":{"nullable":true,"type":"string"},"filename":{"type":"string"}},"required":["filename"],"type":"object"},"type":"array"}},"required":["documents"],"type":"object"},"PrepareDocumentsResponse":{"properties":{"failed_document_ids":{"description":"Ids of documents that failed after their row was created. Preferred over failed_document_indices. Empty when a failure occurred before any row/id existed (a total insert failure) \u2014 those appear only in failed_document_indices.","items":{"type":"integer"},"type":"array"},"failed_document_indices":{"deprecated":true,"description":"DEPRECATED (removed in 1.1.0) \u2014 prefer failed_document_ids. Positions into the request array; retained because a failure with no created row has no id.","items":{"type":"integer"},"type":"array"},"upload_targets":{"items":{"properties":{"document_id":{"example":7,"type":"integer"},"fields":{"additionalProperties":{"type":"string"},"type":"object"},"filename":{"type":"string"},"upload_url":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"SandboxCancelRequest":{"properties":{"cancellation_reason":{"example":"Testing the cancel flow","nullable":true,"type":"string"}},"type":"object"},"Submission":{"properties":{"cancellation_code":{"description":"Stable, machine-readable cancellation reason (present only when status is 'cancelled').","enum":["encrypted_file","unsupported_file","empty_file","outdated_tax_software_file","tax_software_input_mismatch","multiple_tax_software_files","unextractable_zip","mismatched_client","proconnect_link_failed","axcess_client_missing_ssn","unsupported_return_type","cancelled_by_firm","client_deleted","other"],"nullable":true,"type":"string"},"cancellation_reason":{"description":"Human-readable reason (present only when status is 'cancelled').","nullable":true,"type":"string"},"client_id":{"example":42,"type":"integer"},"created_at":{"format":"date-time","type":"string"},"expected_completion_at":{"format":"date-time","nullable":true,"type":"string"},"id":{"example":88,"type":"integer"},"input_documents":{"items":{"$ref":"#/components/schemas/Document"},"type":"array"},"is_resubmission":{"description":"True when this submission inherited from a prior completed one for the same client + tax year.","type":"boolean"},"is_sandbox":{"description":"Present and true ONLY for submissions created in a sandbox organization (never worked by a preparer, no firm emails, no internal notifications). Omitted entirely for normal submissions.","type":"boolean"},"output_documents":{"items":{"$ref":"#/components/schemas/Document"},"type":"array"},"resubmission_of":{"description":"Id of the prior submission this one resubmits, or null.","example":788,"nullable":true,"type":"integer"},"status":{"enum":["received","processing","completed","cancelled"],"type":"string"},"tax_software":{"nullable":true,"type":"string"},"tax_year":{"type":"integer"}},"type":"object"},"SubmissionList":{"properties":{"next_cursor":{"description":"Pass as ?cursor= to fetch the next page. null on the last page.","nullable":true,"type":"string"},"submissions":{"items":{"$ref":"#/components/schemas/SubmissionSummary"},"type":"array"}},"type":"object"},"SubmissionSummary":{"description":"A submission WITHOUT its document arrays \u2014 the shape returned in list responses (GET /v1/submissions). Fetch GET /v1/submissions/{id} for the full object, including input_documents and output_documents.","properties":{"cancellation_code":{"description":"Stable, machine-readable cancellation reason (present only when status is 'cancelled').","enum":["encrypted_file","unsupported_file","empty_file","outdated_tax_software_file","tax_software_input_mismatch","multiple_tax_software_files","unextractable_zip","mismatched_client","proconnect_link_failed","axcess_client_missing_ssn","unsupported_return_type","cancelled_by_firm","client_deleted","other"],"nullable":true,"type":"string"},"cancellation_reason":{"description":"Human-readable reason (present only when status is 'cancelled').","nullable":true,"type":"string"},"client_id":{"example":42,"type":"integer"},"created_at":{"format":"date-time","type":"string"},"expected_completion_at":{"format":"date-time","nullable":true,"type":"string"},"id":{"example":88,"type":"integer"},"is_resubmission":{"description":"True when this submission inherited from a prior completed one for the same client + tax year.","type":"boolean"},"is_sandbox":{"description":"Present and true ONLY for submissions created in a sandbox organization (never worked by a preparer, no firm emails, no internal notifications). Omitted entirely for normal submissions.","type":"boolean"},"resubmission_of":{"description":"Id of the prior submission this one resubmits, or null.","example":788,"nullable":true,"type":"integer"},"status":{"enum":["received","processing","completed","cancelled"],"type":"string"},"tax_software":{"nullable":true,"type":"string"},"tax_year":{"type":"integer"}},"type":"object"},"UpdateClientRequest":{"properties":{"friendly_name":{"type":"string"},"taxpayer_tin":{"nullable":true,"type":"integer"}},"type":"object"}},"securitySchemes":{"bearerAuth":{"description":"Your `mag_...` API key","scheme":"bearer","type":"http"}}},"info":{"description":"Client-centric API for partners. Authenticate with a per-organization API key (`Authorization: Bearer mag_...`); the organization is resolved from the key. Flow: create a client \u2192 upload documents (presigned or multipart) \u2192 create a submission for prep \u2192 poll the submission until `completed`, then download the `output_documents`.\n\n**Webhooks:** configure a `submission_updated_webhook_url` on your API key to receive a signed `submission.updated` event (HMAC-SHA256 in `X-Magnetic-Signature`) whenever a submission's status changes, instead of polling.\n\n**Sandbox:** test the full loop without a real preparer using a sandbox API key (self-managed firms) or a sandbox organization (partners). Sandbox submissions never reach a preparer and send no emails; a completed one returns sample output documents so you can test your download path. Use the Sandbox endpoints to push a submission to `completed`/`cancelled` and exercise your webhook handling end-to-end.","title":"Magnetic Integrations API","version":"1.0.8"},"openapi":"3.0.3","paths":{"/v1/clients":{"get":{"parameters":[{"description":"Page size (default 100, max 500).","in":"query","name":"limit","required":false,"schema":{"default":100,"maximum":500,"minimum":1,"type":"integer"}},{"description":"Opaque pagination token \u2014 pass the `next_cursor` from the previous page. Omit for the first page.","in":"query","name":"cursor","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientList"}}},"description":"OK"}},"summary":"List clients","tags":["Clients"]},"post":{"parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClientRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Client"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Create a client","tags":["Clients"]}},"/v1/clients/{client_id}":{"delete":{"parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Delete (archive) a client","tags":["Clients"]},"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDetail"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"}},"summary":"Get a client (with its submissions)","tags":["Clients"]},"parameters":[{"example":42,"in":"path","name":"client_id","required":true,"schema":{"type":"integer"}}],"patch":{"parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateClientRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Client"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Update a client (e.g. rename)","tags":["Clients"]}},"/v1/clients/{client_id}/documents":{"get":{"description":"Each document with its `state` (uploading / uploaded / submitted), so you can see what has been uploaded and which documents the next submission will take.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/Document"},"type":"array"}},"type":"object"}}},"description":"The client's documents"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"List a client's documents","tags":["Documents"]},"parameters":[{"example":42,"in":"path","name":"client_id","required":true,"schema":{"type":"integer"}}],"post":{"parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"documents":{"items":{"format":"binary","type":"string"},"type":"array"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentList"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Payload too large \u2014 over the 10 MB direct-upload limit; use the presigned flow"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Direct multipart upload (thin-client fallback)","tags":["Documents"]}},"/v1/clients/{client_id}/documents/finalize":{"parameters":[{"example":42,"in":"path","name":"client_id","required":true,"schema":{"type":"integer"}}],"post":{"parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FinalizeDocumentsRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentList"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Confirm presigned uploads completed","tags":["Documents"]}},"/v1/clients/{client_id}/documents/prepare":{"parameters":[{"example":42,"in":"path","name":"client_id","required":true,"schema":{"type":"integer"}}],"post":{"description":"Returns presigned S3 POST targets. Upload each file directly to S3, then call the finalize endpoint.","parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepareDocumentsRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepareDocumentsResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request (e.g. invalid_content_md5)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Request presigned upload targets (DEFAULT ingestion path)","tags":["Documents"]}},"/v1/clients/{client_id}/documents/{document_id}":{"delete":{"description":"Removes a document. Refused (409) once the document is attached to a submission.","parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deleted":{"type":"boolean"},"id":{"type":"integer"}},"type":"object"}}},"description":"Deleted"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Attached to a submission. Also returned for an Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Delete a document","tags":["Documents"]},"parameters":[{"example":42,"in":"path","name":"client_id","required":true,"schema":{"type":"integer"}},{"in":"path","name":"document_id","required":true,"schema":{"type":"integer"}}]},"/v1/clients/{client_id}/submissions":{"parameters":[{"example":42,"in":"path","name":"client_id","required":true,"schema":{"type":"integer"}}],"post":{"description":"Creates a submission from the client's uploaded input documents. If the client already has a completed submission for the same tax year, this one is automatically treated as a resubmission (`is_resubmission: true`, `resubmission_of` set) \u2014 upload any new/corrected documents first.","parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubmissionRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`active_submission_exists` or `documents_still_uploading`. Also returned for an Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Create a submission for prep","tags":["Submissions"]}},"/v1/organization":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"}}},"description":"OK"}},"summary":"Who am I \u2014 confirm org, owner, and scopes for this key","tags":["Organization"]}},"/v1/submissions":{"get":{"parameters":[{"example":42,"in":"query","name":"client_id","required":false,"schema":{"type":"integer"}},{"in":"query","name":"status","required":false,"schema":{"enum":["received","processing","completed","cancelled"],"type":"string"}},{"description":"Page size (default 100, max 500).","in":"query","name":"limit","required":false,"schema":{"default":100,"maximum":500,"minimum":1,"type":"integer"}},{"description":"Opaque pagination token \u2014 pass the `next_cursor` from the previous page. Omit for the first page.","in":"query","name":"cursor","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionList"}}},"description":"OK"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 poll less / use webhooks"}},"summary":"List submissions","tags":["Submissions"]}},"/v1/submissions/{submission_id}":{"get":{"description":"Rate-limited per API key. Prefer webhooks (`submission_updated_webhook_url`) over polling; a 429 with `Retry-After` means back off.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 poll less / use webhooks"}},"summary":"Poll a submission (status + output documents)","tags":["Submissions"]},"parameters":[{"example":88,"in":"path","name":"submission_id","required":true,"schema":{"type":"integer"}}]},"/v1/submissions/{submission_id}/cancel":{"parameters":[{"example":88,"in":"path","name":"submission_id","required":true,"schema":{"type":"integer"}}],"post":{"description":"Cancel a submission within 30 minutes of creating it. After the window it must run to completion. Optional body `{\"cancellation_reason\": \"...\"}`. Fires your submission webhook.","parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxCancelRequest"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Already terminal or past the cancellation window. Also returned for an Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Cancel a submission (within 30 minutes)","tags":["Submissions"]}},"/v1/submissions/{submission_id}/sandbox/cancel":{"parameters":[{"example":88,"in":"path","name":"submission_id","required":true,"schema":{"type":"integer"}}],"post":{"description":"Available ONLY for submissions in a sandbox organization. Optional body `{\"cancellation_reason\": \"...\"}`. Fires your submission webhook but sends no firm emails.","parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxCancelRequest"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}},"description":"OK"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not a sandbox org"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Already terminal. Also returned for an Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Sandbox only: force a submission to 'cancelled'","tags":["Sandbox"]}},"/v1/submissions/{submission_id}/sandbox/complete":{"parameters":[{"example":88,"in":"path","name":"submission_id","required":true,"schema":{"type":"integer"}}],"post":{"description":"Available ONLY for submissions in a sandbox organization. Pushes the submission to 'completed' so you can test the full loop without a real preparer; fires your submission webhook but sends no firm emails.","parameters":[{"description":"Optional. Retry a write with the same key and you get the ORIGINAL response back instead of the work happening twice \u2014 so a request that timed out is safe to repeat. Use one key per logical operation (a UUID), and reuse it for every retry of that operation. Keys are scoped to your API key and honoured for 24 hours. A replayed response carries the `Idempotent-Replayed: true` header \u2014 the body is byte-identical to the original, so that is the only way to tell stored work from fresh work. Reusing a key with a different request body returns 409 `idempotency_key_reuse`; replaying while the first request is still running returns 409 `idempotency_request_in_progress`. An empty header, or one over 255 characters, is rejected with 400 `invalid_idempotency_key` \u2014 omit it entirely to opt out.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":255,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}},"description":"OK"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not a sandbox org"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Already terminal. Also returned for an Idempotency-Key conflict \u2014 reused for a different request (`idempotency_key_reuse`), or the original request is still running (`idempotency_request_in_progress`)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited \u2014 retry after the `Retry-After` delay"}},"summary":"Sandbox only: force a submission to 'completed'","tags":["Sandbox"]}}},"security":[{"bearerAuth":[]}],"servers":[{"url":"/"}],"tags":[{"name":"Organization"},{"name":"Clients"},{"name":"Documents"},{"name":"Submissions"},{"description":"Sandbox-only controls for testing the full loop against a sandbox submission (from a sandbox key or sandbox org).","name":"Sandbox"}]}
