Quickstart
This walks the full loop: client → documents → submission → output documents. Examples use curl; any HTTP client works.
Before you begin — get an API key. A firm admin creates one in the Magnetic portal under Preferences → Self-managed API → Create API key (shown once); a partner issues a firm's key from the partner console (see Partners). No Magnetic account yet? Contact your Magnetic representative. See Authentication for details.
export MAGNETIC_API_KEY=mag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export BASE=https://api.magnetictax.com # same base URL for sandbox — your sandbox key sets the context
auth=(-H "Authorization: Bearer $MAGNETIC_API_KEY")
1. Create a client
A client is the taxpayer the return is for.
curl -s "${auth[@]}" -H 'Content-Type: application/json' \
-d '{"friendly_name":"Jane Q. Taxpayer"}' \
$BASE/v1/clients
{ "id": 123, "friendly_name": "Jane Q. Taxpayer", "taxpayer_tin": null, "created_at": "2026-06-23T20:00:00Z" }
Keep the id (123).
2. Upload documents
The recommended path is the presigned exchange: you upload bytes straight to storage, so large files never tie up the API. (A simpler one-shot multipart upload is also available.)
a. Ask for upload targets
PREPARE_RESPONSE=$(curl -s "${auth[@]}" -H 'Content-Type: application/json' \
-d '{"documents":[{"filename":"W2.pdf","content_type":"application/pdf"}]}' \
$BASE/v1/clients/123/documents/prepare)
echo "$PREPARE_RESPONSE"
{
"upload_targets": [
{ "document_id": 456, "filename": "W2.pdf",
"upload_url": "https://s3-us-west-2.amazonaws.com/...",
"fields": { "key": "...", "policy": "...", "x-amz-signature": "...", "…": "…" } }
],
"failed_document_ids": [],
"failed_document_indices": []
}
Read failures from failed_document_ids; failed_document_indices is deprecated (see Deprecations).
b. Upload each file to the returned URL — POST every key/value in fields (there are more than the three shown), then the file part last, as multipart/form-data. Missing a field gets you an S3 403:
# `fields` is an object of form fields S3 requires — send ALL of them, then the file LAST.
# Build the -F flags from the response so you can't miss one:
mapfile -t FIELDS < <(echo "$PREPARE_RESPONSE" | jq -r '.upload_targets[0].fields | to_entries[] | "-F\n\(.key)=\(.value)"')
UPLOAD_URL=$(echo "$PREPARE_RESPONSE" | jq -r '.upload_targets[0].upload_url')
# Quoted array expansion: a policy value contains base64 padding and slashes, which unquoted
# word splitting and globbing would mangle.
curl -s -X POST "$UPLOAD_URL" "${FIELDS[@]}" -F [email protected]
# → HTTP 204 (no body). Any missing field gets you a 403 from S3.
c. Finalize to confirm the uploads:
curl -s "${auth[@]}" -H 'Content-Type: application/json' \
-d '{"document_ids":[456]}' \
$BASE/v1/clients/123/documents/finalize
3. Create a submission
tax_software and tax_year are required. See valid tax_software values. (ProConnect also requires two ids — see Submissions.) A client can only have one active submission at a time; creating a second while one is in flight returns 409.
curl -s "${auth[@]}" -H 'Content-Type: application/json' \
-d '{"tax_software":"Drake","tax_year":2025}' \
$BASE/v1/clients/123/submissions
{
"id": 789,
"client_id": 123,
"status": "processing",
"tax_software": "Drake",
"tax_year": 2025,
"is_resubmission": false,
"resubmission_of": null,
"expected_completion_at": "2026-06-26T20:00:00Z",
"input_documents": [ { "id": 456, "filename": "W2.pdf" } ],
"output_documents": []
}
expected_completion_at is the date you can expect the finished return by. If this client already has a completed return for the same tax_year, the new submission is automatically treated as a resubmission (is_resubmission: true).
4. Get notified when it's completed
Webhooks are the recommended way to track a submission's status. Set a callback URL (submission_updated_webhook_url) on your API key — a firm in the portal; a partner from the firm's key in the partner console or via the Partner API — and we POST a signed submission.updated event on every status change — so you're notified the moment the return is ready, with no polling loop to run. See Webhooks.
Polling GET /v1/submissions/{id} is a supported fallback. If you can't receive webhooks, fetch the submission periodically — a few times a day (processing takes ~3 days), not in a tight loop:
curl -s "${auth[@]}" $BASE/v1/submissions/789
When status becomes completed, output_documents are populated with ready-to-use download_urls:
{
"id": 789,
"status": "completed",
"output_documents": [
{ "id": 999, "filename": "..._Return.DI5", "category": "tax_return", "download_url": "https://s3-us-west-2.amazonaws.com/..." },
{ "id": 998, "filename": "workpapers.pdf", "category": "workpapers", "download_url": "https://s3-us-west-2.amazonaws.com/..." },
{ "id": 997, "filename": "..._notes.txt", "category": "review_notes", "download_url": "https://s3-us-west-2.amazonaws.com/..." },
{ "id": 996, "filename": "..._notes.pdf", "category": "review_notes", "download_url": "https://s3-us-west-2.amazonaws.com/..." }
]
}
5. Download the outputs
# Save each output under its own filename — the tax_return is the software's native file
# (e.g. a Drake .DI5), not a PDF, so don't hard-code a .pdf extension.
curl -sL "<output_documents[0].download_url>" -o "<output_documents[0].filename>"
That's the full round trip. For details on each step see Uploading documents and Submissions & statuses.
Try it live in Swagger
The interactive API reference at /v1/docs lets you run these calls against your own key from the browser:
- Open
/v1/docs. - Click Authorize (top right) and paste your key as
Bearer mag_…. - Expand any endpoint → Try it out → Execute. The response panel shows the real status code, headers, and body for your org.
Use your sandbox key here to exercise the full create → upload → submit → sandbox/complete loop (see Sandbox testing) without touching real returns.