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:

  1. Open /v1/docs.
  2. Click Authorize (top right) and paste your key as Bearer mag_….
  3. Expand any endpoint → Try it outExecute. 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.

results matching ""

    No results matching ""