API DOCUMENTATION

API reference

Version 2026-09-06. Base URL https://api.taxpio.com. The machine-readable contract is at /openapi.json.

Platform

GET /v1

API index: version, documentation and where to start.

No scope

Responses: 200 The index.

GET /v1/health

Liveness: the release and its database answer.

No scope

Responses: 200 Healthy. · 503 The database does not answer.

GET /v1/me

The workspace, application, environment and scopes behind the key.

No scope

Responses: 200 The principal. · 401 401 · 403 403 · 429 429

Tax calculations

GET /v1/tax/programs

The calculation programs and their inputs.

No scope

Responses: 200 The programs. · 401 401 · 429 429

POST /v1/tax/calculations

Run a deterministic tax calculation. Deterministic: the same request always returns the same lines. Send an Idempotency-Key to make retries safe; a replay carries `Idempotent-Replayed: true`.

Scope calculations:write · accepts Idempotency-Key

ParameterInRequiredType
Idempotency-Keyheadernostring
Request body schema
{
  "type": "object",
  "required": [
    "jurisdiction",
    "tax_year",
    "inputs"
  ],
  "properties": {
    "jurisdiction": {
      "type": "string",
      "enum": [
        "CA"
      ]
    },
    "program": {
      "type": "string",
      "enum": [
        "T1",
        "T2",
        "1120",
        "CT600",
        "CIT"
      ],
      "description": "Required for CA; defaults to the jurisdiction's program elsewhere."
    },
    "tax_year": {
      "type": "integer",
      "minimum": 2020,
      "maximum": 2035
    },
    "inputs": {
      "oneOf": [
        {
          "title": "CA/T1",
          "type": "object",
          "required": [
            "facts"
          ],
          "properties": {
            "facts": {
              "type": "object",
              "description": "Personal facts: province, dateOfBirth, maritalStatus, eligibleDependant, …"
            },
            "slips": {
              "type": "array",
              "items": {
                "type": "object",
                "required": [
                  "kind"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "T4",
                      "T4A",
                      "T4A_OAS",
                      "T4A_P",
                      "T4E",
                      "T5",
                      "T3",
                      "T5008",
                      "T2202",
                      "RRSP_RECEIPT",
                      "T2125",
                      "T776"
                    ]
                  }
                },
                "additionalProperties": true
              }
            },
            "claims": {
              "type": "object",
              "description": "rrspDeduction, professionalDues, childCareExpenses, otherEmploymentExpenses, medicalExpenses, charitableDonations, studentLoanInterest as decimal strings."
            }
          },
          "description": "inputs = { facts, slips, claims } exactly as TaxPio's T1 rule package takes them: money as decimal strings in dollars, slips typed by kind (T4, T4A, T5, …)."
        }
      ]
    }
  }
}

Responses: 201 The calculation. · 400 400 · 401 401 · 403 403 · 409 409 · 413 413 · 415 415 · 422 422 · 429 429

GET /v1/tax/calculations

List calculations, newest first.

Scope calculations:read

ParameterInRequiredType
limitquerynointeger
cursorquerynostring · The previous page's next_cursor.

Responses: 200 A page of calculations. · 400 400 · 401 401 · 403 403 · 429 429

GET /v1/tax/calculations/{calculationId}

Fetch one calculation.

Scope calculations:read

ParameterInRequiredType
calculationIdpathyesstring

Responses: 200 The calculation. · 401 401 · 403 403 · 404 404 · 429 429

Sandbox

GET /v1/sandbox/scenarios

Deterministic sandbox scenarios for every program and outcome. Sandbox keys only (tp_test_…). A live key receives 403 SANDBOX_ONLY.

No scope

Responses: 200 The scenarios. · 401 401 · 403 403 · 429 429

POST /v1/sandbox/scenarios/{scenarioId}/run

Run a scenario as a real calculation in the sandbox.

Scope calculations:write · accepts Idempotency-Key

ParameterInRequiredType
scenarioIdpathyesstring
Idempotency-Keyheadernostring

Responses: 201 The calculation, with the scenario it came from. · 401 401 · 403 403 · 404 404 · 409 409 · 429 429

POST /v1/sandbox/webhook-sink/{endpointId}

A receiver on our host that verifies TaxPio's signature. Register a sandbox endpoint whose URL is https://api.taxpio.com/v1/sandbox/webhook-sink/{endpointId} (the id you get back) and every delivery lands here, is verified with your endpoint's secret, and shows in the trace.

No scope

ParameterInRequiredType
endpointIdpathyesstring
Taxpio-Signatureheaderyesstring

Responses: 200 Received and verified. · 401 401 · 413 413

Webhooks

GET /v1/webhooks

List webhook endpoints.

Scope webhooks:manage

Responses: 200 The endpoints. · 401 401 · 403 403 · 429 429

POST /v1/webhooks

Register an endpoint; the signing secret is returned once.

Scope webhooks:manage · accepts Idempotency-Key

Request body schema
{
  "type": "object",
  "required": [
    "url"
  ],
  "properties": {
    "url": {
      "type": "string",
      "format": "uri",
      "description": "https only; a public host, or the sandbox sink on api.taxpio.com."
    },
    "description": {
      "type": "string",
      "maxLength": 500
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "calculation.completed",
          "key.revoked",
          "webhook.test"
        ]
      },
      "description": "Defaults to every event."
    }
  }
}

Responses: 201 The endpoint with its secret (shown once). · 401 401 · 403 403 · 409 409 · 422 422 · 429 429

GET /v1/webhooks/{endpointId}

Fetch one endpoint.

Scope webhooks:manage

ParameterInRequiredType
endpointIdpathyesstring

Responses: 200 The endpoint. · 401 401 · 403 403 · 404 404 · 429 429

DELETE /v1/webhooks/{endpointId}

Remove an endpoint and its deliveries.

Scope webhooks:manage

ParameterInRequiredType
endpointIdpathyesstring

Responses: 200 Deleted. · 401 401 · 403 403 · 404 404 · 429 429

POST /v1/webhooks/{endpointId}/test

Queue a webhook.test delivery.

Scope webhooks:manage

ParameterInRequiredType
endpointIdpathyesstring

Responses: 202 Queued; the runner delivers within half a minute. · 401 401 · 403 403 · 404 404 · 409 409 · 429 429

GET /v1/webhooks/{endpointId}/deliveries

Recent deliveries to an endpoint.

Scope webhooks:manage

ParameterInRequiredType
endpointIdpathyesstring
limitquerynointeger

Responses: 200 The deliveries. · 401 401 · 403 403 · 404 404 · 429 429

Connections

GET /v1/connections

Connections to client profiles.

Scope connections:manage

ParameterInRequiredType
limitquerynointeger
cursorquerynostring

Responses: 200 A page of connections. · 400 400 · 401 401 · 403 403 · 429 429

POST /v1/connections

Invite a client to connect a profile. TaxPio emails the client a link; they choose which of their profiles to connect and which permissions to grant. The connection is INVITED until then, and can be revoked by either side.

Scope connections:manage · accepts Idempotency-Key

Request body schema
{
  "type": "object",
  "required": [
    "client_email",
    "system_name"
  ],
  "properties": {
    "client_email": {
      "type": "string",
      "format": "email"
    },
    "client_name": {
      "type": "string"
    },
    "category": {
      "type": "string",
      "enum": [
        "ACCOUNTING",
        "ERP",
        "ECOMMERCE",
        "PAYROLL",
        "PAYMENTS",
        "CUSTOM"
      ]
    },
    "system_name": {
      "type": "string",
      "description": "The system the records come from, e.g. QuickBooks Online, Shopify, Gusto."
    },
    "permissions": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "records.import",
          "records.read",
          "documents.write"
        ]
      },
      "description": "Defaults to all three."
    }
  }
}

Responses: 201 The invited connection. · 401 401 · 403 403 · 422 422 · 429 429

GET /v1/connections/{connectionId}

Fetch one connection.

Scope connections:manage

ParameterInRequiredType
connectionIdpathyesstring

Responses: 200 The connection. · 401 401 · 403 403 · 404 404 · 429 429

DELETE /v1/connections/{connectionId}

End a connection.

Scope connections:manage

ParameterInRequiredType
connectionIdpathyesstring

Responses: 200 Revoked. · 401 401 · 403 403 · 404 404 · 429 429

POST /v1/connections/{connectionId}/records

Push up to 500 records into the connected profile. Each record is identified by your external_id; a repeat of the same id is counted as a duplicate and never written twice. Amounts are signed decimal strings in the profile's currency: negative for money out, positive for money in. Records land as unreviewed transactions with receipt-match suggestions, exactly as a bank sync does.

Scope records:import · accepts Idempotency-Key

ParameterInRequiredType
connectionIdpathyesstring
Idempotency-Keyheadernostring
Request body schema
{
  "type": "object",
  "required": [
    "records"
  ],
  "properties": {
    "records": {
      "type": "array",
      "maxItems": 500,
      "items": {
        "$ref": "#/components/schemas/RecordInput"
      }
    }
  }
}

Responses: 201 The import summary: inserted, duplicates, suggestions, affected_tax_years. · 401 401 · 403 403 · 404 404 · 409 409 · 422 422 · 429 429

GET /v1/connections/{connectionId}/records

Records this connection pushed.

Scope records:read

ParameterInRequiredType
connectionIdpathyesstring
limitquerynointeger
cursorquerynostring

Responses: 200 A page of records. · 400 400 · 401 401 · 403 403 · 404 404 · 409 409 · 429 429

POST /v1/connections/{connectionId}/documents

Upload a document into the connected profile. multipart/form-data with the file (PDF, JPEG, PNG or WebP, up to 15 MB) plus optional metadata; the vault's pipeline and AI suggestions run as for any capture.

Scope documents:write

ParameterInRequiredType
connectionIdpathyesstring
Idempotency-Keyheadernostring

Responses: 201 The stored document. · 401 401 · 403 403 · 404 404 · 409 409 · 413 413 · 422 422 · 429 429

Usage and credits

GET /v1/usage

Requests per day and operation.

Scope usage:read

ParameterInRequiredType
fromquerynostring
toquerynostring

Responses: 200 Usage for the window (at most 92 days). · 400 400 · 401 401 · 403 403 · 429 429

GET /v1/credits

Credit balance and recent movements. Credits pay for billable filing actions on live keys and are consumed once per request (the request id is the idempotency key). A live call that needs more credits than the workspace holds answers 402 CREDITS_REQUIRED. Sandbox never bills.

Scope usage:read

Responses: 200 The balance. · 401 401 · 403 403 · 429 429

Schemas

Error
{
  "type": "object",
  "required": [
    "error",
    "code",
    "requestId"
  ],
  "properties": {
    "error": {
      "type": "string",
      "description": "A sentence a person can read."
    },
    "code": {
      "type": "string",
      "enum": [
        "BAD_REQUEST",
        "UNAUTHENTICATED",
        "PAYMENT_REQUIRED",
        "FORBIDDEN",
        "NOT_FOUND",
        "METHOD_NOT_ALLOWED",
        "NOT_ACCEPTABLE",
        "REQUEST_TIMEOUT",
        "CONFLICT",
        "GONE",
        "PAYLOAD_TOO_LARGE",
        "UNSUPPORTED_MEDIA_TYPE",
        "VALIDATION_FAILED",
        "LOCKED",
        "PRECONDITION_REQUIRED",
        "RATE_LIMITED",
        "INTERNAL",
        "NOT_IMPLEMENTED",
        "UNAVAILABLE",
        "TIMEOUT",
        "ORIGIN_REQUIRED",
        "PASSKEY_REQUIRED",
        "PASSKEY_ENROLLMENT_REQUIRED",
        "RECENT_MFA_REQUIRED",
        "RECENT_AUTHENTICATION_REQUIRED",
        "ACCOUNT_PASSWORD_REQUIRED",
        "EMAIL_VERIFICATION_REQUIRED",
        "VERIFICATION_DELIVERY_FAILED",
        "RESOURCE_ROUTE_UNAVAILABLE",
        "MALWARE_DETECTED",
        "NONDETERMINISTIC_PROVIDER_RETRY",
        "CREDITS_REQUIRED",
        "API_KEY_MISSING",
        "API_KEY_INVALID",
        "API_KEY_REVOKED",
        "API_KEY_EXPIRED",
        "WORKSPACE_SUSPENDED",
        "APPLICATION_ARCHIVED",
        "SCOPE_REQUIRED",
        "PRODUCTION_REVIEW_REQUIRED",
        "IDEMPOTENCY_KEY_INVALID",
        "IDEMPOTENCY_KEY_REUSED",
        "IDEMPOTENCY_KEY_IN_PROGRESS",
        "BODY_INVALID",
        "UNSUPPORTED_PROGRAM",
        "SANDBOX_ONLY",
        "CONNECTION_PENDING",
        "CONNECTION_REVOKED",
        "CONNECTION_PERMISSION_REQUIRED",
        "LIVE_ONLY",
        "WEBHOOK_SIGNATURE_INVALID"
      ],
      "description": "What a client branches on."
    },
    "requestId": {
      "type": "string",
      "description": "Quote it to support; it is also the x-request-id response header."
    },
    "docs_url": {
      "type": "string",
      "format": "uri"
    },
    "problems": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  }
}
Calculation
{
  "type": "object",
  "required": [
    "id",
    "object",
    "environment",
    "jurisdiction",
    "program",
    "tax_year",
    "rule_package",
    "currency",
    "scope",
    "reasons",
    "notes",
    "lines",
    "inputs",
    "input_hash",
    "created_at"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^calc_[A-Za-z0-9]{20,32}$"
    },
    "object": {
      "type": "string",
      "enum": [
        "tax.calculation"
      ]
    },
    "environment": {
      "type": "string",
      "enum": [
        "live",
        "sandbox"
      ]
    },
    "jurisdiction": {
      "type": "string",
      "enum": [
        "CA"
      ]
    },
    "program": {
      "type": "string",
      "enum": [
        "T1",
        "T2"
      ]
    },
    "tax_year": {
      "type": "integer"
    },
    "rule_package": {
      "type": "string",
      "description": "The versioned rule package that produced the answer."
    },
    "currency": {
      "type": "string"
    },
    "scope": {
      "type": "string",
      "enum": [
        "IN_SCOPE",
        "OUT_OF_SCOPE",
        "INCOMPLETE"
      ],
      "description": "OUT_OF_SCOPE: the inputs describe a case the package refuses, with reasons. INCOMPLETE: CA/T1 with blocking issues."
    },
    "reasons": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "notes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "lines": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "key",
          "label"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "amount_minor": {
            "type": "integer",
            "description": "Minor units (cents, pence, kobo) for preview programs."
          },
          "amount": {
            "type": "string",
            "description": "Decimal string in the currency for CA/T1."
          },
          "text": {
            "type": "string"
          }
        }
      }
    },
    "summary": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    },
    "issues": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "severity": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "fix_action": {
            "type": "string"
          }
        }
      }
    },
    "inputs": {
      "type": "object"
    },
    "input_hash": {
      "type": "string"
    },
    "created_at": {
      "type": "string",
      "format": "date-time"
    }
  }
}
WebhookEndpoint
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "object": {
      "type": "string",
      "enum": [
        "webhook_endpoint"
      ]
    },
    "environment": {
      "type": "string",
      "enum": [
        "live",
        "sandbox"
      ]
    },
    "url": {
      "type": "string"
    },
    "description": {
      "type": "string",
      "nullable": true
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "status": {
      "type": "string",
      "enum": [
        "ACTIVE",
        "DISABLED"
      ]
    },
    "secret_prefix": {
      "type": "string"
    },
    "consecutive_failures": {
      "type": "integer"
    },
    "last_delivery_at": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string"
    }
  }
}
WebhookDelivery
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "object": {
      "type": "string",
      "enum": [
        "webhook_delivery"
      ]
    },
    "endpoint_id": {
      "type": "string"
    },
    "event_id": {
      "type": "string"
    },
    "event_type": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "PENDING",
        "DELIVERED",
        "FAILED",
        "DEAD"
      ]
    },
    "attempts": {
      "type": "integer"
    },
    "next_attempt_at": {
      "type": "string",
      "nullable": true
    },
    "last_status": {
      "type": "integer",
      "nullable": true
    },
    "last_error": {
      "type": "string",
      "nullable": true
    },
    "delivered_at": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string"
    }
  }
}
Event
{
  "type": "object",
  "description": "The body of every webhook delivery, signed with `Taxpio-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.",
  "properties": {
    "id": {
      "type": "string"
    },
    "object": {
      "type": "string",
      "enum": [
        "event"
      ]
    },
    "type": {
      "type": "string",
      "enum": [
        "calculation.completed",
        "key.revoked",
        "webhook.test"
      ]
    },
    "created": {
      "type": "string"
    },
    "environment": {
      "type": "string"
    },
    "workspace_id": {
      "type": "string"
    },
    "data": {
      "type": "object"
    },
    "attempt": {
      "type": "integer"
    }
  }
}
Connection
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "object": {
      "type": "string",
      "enum": [
        "connection"
      ]
    },
    "environment": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "INVITED",
        "ACTIVE",
        "REVOKED",
        "DECLINED",
        "EXPIRED"
      ]
    },
    "category": {
      "type": "string"
    },
    "system_name": {
      "type": "string"
    },
    "permissions": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "client_email": {
      "type": "string",
      "nullable": true
    },
    "client_name": {
      "type": "string",
      "nullable": true
    },
    "profile": {
      "type": "object",
      "nullable": true,
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "granted_at": {
      "type": "string",
      "nullable": true
    },
    "revoked_at": {
      "type": "string",
      "nullable": true
    },
    "expires_at": {
      "type": "string",
      "nullable": true
    },
    "last_import_at": {
      "type": "string",
      "nullable": true
    },
    "records_imported": {
      "type": "integer"
    },
    "documents_imported": {
      "type": "integer"
    },
    "created_at": {
      "type": "string"
    }
  }
}
RecordInput
{
  "type": "object",
  "required": [
    "external_id",
    "occurred_at",
    "description",
    "amount"
  ],
  "properties": {
    "external_id": {
      "type": "string",
      "maxLength": 120,
      "description": "Your stable id for the record."
    },
    "occurred_at": {
      "type": "string",
      "format": "date"
    },
    "description": {
      "type": "string",
      "maxLength": 240
    },
    "amount": {
      "type": "string",
      "description": "Signed decimal string, at most two decimals, non-zero."
    },
    "counterparty": {
      "type": "string",
      "maxLength": 200,
      "description": "Folded into the description when the description does not already name it; TaxPio's own counterparty is set when the owner reviews the record."
    },
    "category": {
      "type": "string",
      "maxLength": 80,
      "description": "Kept as a hint in the import's audit trail; TaxPio's category and tax treatment are set when the owner reviews the record."
    }
  }
}
Principal
{
  "type": "object",
  "properties": {
    "object": {
      "type": "string",
      "enum": [
        "principal"
      ]
    },
    "workspace": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "application": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "environment": {
      "type": "string",
      "enum": [
        "live",
        "sandbox"
      ]
    },
    "scopes": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "calculations:read",
          "calculations:write",
          "connections:manage",
          "records:import",
          "records:read",
          "documents:read",
          "documents:write",
          "webhooks:manage",
          "usage:read"
        ]
      }
    },
    "rate_limit_per_minute": {
      "type": "integer"
    }
  }
}