ODOV Tax Forms API

Go to Main App →

Need API access or technical support?Contact us

Introduction

The ODOV Tax Forms API provides programmatic access to tax form due dates and calculation rules. This documentation covers both API versions currently supported.

Base URL: https://tax-forms.odov.io/api

Authentication

All API requests must include your API key in the request header:

X-API-Key: your-api-key-here

Rate Limiting: API keys are limited to 100 requests per 10 minutes. Contact support if you need higher limits.

API Version 2Recommended

POST/api/v2/calculate

Calculate due dates for a specific tax form

Request Body

{
  "formNumber": "1040",
  "entityType": "individual",
  "locality": "United States",
  "localityType": "federal",
  "coverageStartDate": "2024-01-01",
  "coverageEndDate": "2024-12-31"
}

Response

{
  "formNumber": "1040",
  "formName": "U.S. Individual Income Tax Return",
  "calculationBase": "end",
  "dates": {
    "dueDate": "2025-04-15T23:00:00.000Z",
    "extensionDueDate": "2025-10-15T23:00:00.000Z"
  },
  "requestedExtensionLevel": "1",
  "extensions": {
    "1": {
      "formNumber": "4868",
      "formName": "Application for Automatic Extension of Time to File U.S. Individual Income Tax Return",
      "extensionType": "automatic",
      "filingRequired": true,
      "paymentRequired": true,
      "piggybackedBy": [
        {
          "formNumber": "IT-303",
          "locality": "Georgia"
        }
      ]
    }
  }
}

Example Request

curl -X POST https://tax-forms.odov.io/api/v2/calculate \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "formNumber": "1040", "entityType": "individual", "locality": "United States", "localityType": "federal", "coverageStartDate": "2024-01-01", "coverageEndDate": "2024-12-31" }'
const response = await fetch('https://tax-forms.odov.io/api/v2/calculate', { method: 'POST', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, body: JSON.stringify({ formNumber: '1040', entityType: 'individual', locality: 'United States', localityType: 'federal', coverageStartDate: '2024-01-01', coverageEndDate: '2024-12-31' }) }); const data = await response.json(); console.log(data);
import requests response = requests.post( 'https://tax-forms.odov.io/api/v2/calculate', headers={ 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, json={ 'formNumber': '1040', 'entityType': 'individual', 'locality': 'United States', 'localityType': 'federal', 'coverageStartDate': '2024-01-01', 'coverageEndDate': '2024-12-31' } ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v2/calculate') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri) request['X-API-Key'] = 'your-api-key-here' request['Content-Type'] = 'application/json' request.body = { formNumber: '1040', entityType: 'individual', locality: 'United States', localityType: 'federal', coverageStartDate: '2024-01-01', coverageEndDate: '2024-12-31' }.to_json response = http.request(request) data = JSON.parse(response.body) puts data
GET/api/v2/forms

List all available forms with their calculation rules

Query Parameters

ParameterTypeDescription
entityTypestringFilter by entity type (individual, corporation, etc.)
localityTypestringFilter by locality type (federal, state, city)
localitystringFilter by specific locality (e.g., "California")
sincestringISO 8601 date - Get only forms added/modified after this date

Response

{
  "forms": [
    {
      "formNumber": "1040",
      "formName": "U.S. Individual Income Tax Return",
      "entityType": "individual",
      "localityType": "federal",
      "locality": "Federal",
      "parentFormNumbers": [],
      "calculationRules": [
        {
          "years": [2024, 2025],
          "dueDate": {
            "type": "fixed",
            "monthsAfterPeriodEnd": 3,
            "dayOfMonth": 15
          },
          "extensions": {
            "available": true,
            "automatic": true,
            "automaticMonths": 6
          }
        }
      ]
    }
  ],
  "metadata": {
    "version": "2.0",
    "lastUpdated": "2024-01-15T10:30:00Z",
    "totalForms": 150
  }
}

Example Request

curl -X GET "https://tax-forms.odov.io/api/v2/forms?entityType=individual&localityType=federal" \ -H "X-API-Key: your-api-key-here"
const response = await fetch('https://tax-forms.odov.io/api/v2/forms?entityType=individual&localityType=federal', { headers: { 'X-API-Key': 'your-api-key-here' } }); const data = await response.json(); console.log(data);
import requests response = requests.get( 'https://tax-forms.odov.io/api/v2/forms', headers={'X-API-Key': 'your-api-key-here'}, params={ 'entityType': 'individual', 'localityType': 'federal' } ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v2/forms') uri.query = URI.encode_www_form({ entityType: 'individual', localityType: 'federal' }) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts data
GET/api/v2/forms?since={ISO-8601-date}

Track forms that have been added or modified after a specific date

Description

This endpoint is perfect for keeping your local database in sync. Pass an ISO 8601 date to get only forms that have been added or modified since that date.

Query Parameters

ParameterRequiredDescription
sinceYesISO 8601 date (e.g., 2024-01-01T00:00:00Z)
entityTypeNoFilter by entity type
localityTypeNoFilter by locality type

Response

{
  "forms": [
    {
      "formNumber": "941",
      "formName": "Employer's Quarterly Federal Tax Return",
      "localityType": "federal",
      "locality": "United States",
      "entityType": "business",
      "lastModified": "2024-03-15T14:30:00Z",
      // ... other form fields
    }
  ],
  "metadata": {
    "version": "2.0",
    "lastUpdated": "2024-03-15T14:30:00Z"
  },
  "changesCount": 1,
  "since": "2024-01-01T00:00:00Z"
}

Example Request

curl -X GET "https://tax-forms.odov.io/api/v2/forms?since=2024-01-01T00:00:00Z" \ -H "X-API-Key: your-api-key-here"
// Get forms modified in the last 7 days const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); const response = await fetch( `https://tax-forms.odov.io/api/v2/forms?since=${sevenDaysAgo.toISOString()}`, { headers: { 'X-API-Key': 'your-api-key-here' } } ); const data = await response.json(); console.log(`Found ${data.changesCount} changed forms`);
import requests from datetime import datetime, timedelta # Get forms modified in the last 7 days seven_days_ago = (datetime.now() - timedelta(days=7)).isoformat() + 'Z' response = requests.get( 'https://tax-forms.odov.io/api/v2/forms', headers={'X-API-Key': 'your-api-key-here'}, params={'since': seven_days_ago} ) data = response.json() print(f"Found {data['changesCount']} changed forms")
require 'net/http' require 'json' require 'time' # Get forms modified in the last 7 days seven_days_ago = (Time.now - 7 * 24 * 60 * 60).iso8601 uri = URI('https://tax-forms.odov.io/api/v2/forms') uri.query = URI.encode_www_form({since: seven_days_ago}) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts "Found #{data['changesCount']} changed forms"
💡 Sync Strategy
  1. Store the lastUpdated timestamp from each response
  2. Use that timestamp as the since parameter in your next request
  3. Process only the returned forms to update your local database
  4. This ensures you never miss updates and minimize API calls
GET/api/v2/forms/{formNumber}

Get detailed information about a specific form

Query Parameters

ParameterRequiredDescription
entityTypeSometimesRequired if form exists for multiple entity types
localitySometimesRequired for state/local forms

Example Request

curl -X GET "https://tax-forms.odov.io/api/v2/forms/1040?entityType=individual" \ -H "X-API-Key: your-api-key-here"
const response = await fetch('https://tax-forms.odov.io/api/v2/forms/1040?entityType=individual', { headers: { 'X-API-Key': 'your-api-key-here' } }); const data = await response.json(); console.log(data);
import requests response = requests.get( 'https://tax-forms.odov.io/api/v2/forms/1040', headers={'X-API-Key': 'your-api-key-here'}, params={'entityType': 'individual'} ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v2/forms/1040') uri.query = URI.encode_www_form({entityType: 'individual'}) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts data
POST/api/v2/calculate-simpleNew!

Calculate due dates with just a form number - returns all variations

Description

Simplified calculation endpoint that only requires a form number. If the form exists for multiple entity types or localities, it returns calculations for all variations.

Request Body

{
  "formNumber": "1040",
  "coverageStartDate": "2024-01-01",
  "coverageEndDate": "2024-12-31"
}

Response (Single Variation)

{
  "formNumber": "1040",
  "formName": "U.S. Individual Income Tax Return",
  "entityType": "individual",
  "locality": "United States",
  "localityType": "federal",
  "calculationBase": "end",
  "dates": {
    "dueDate": "2025-04-15T23:00:00.000Z",
    "extensionDueDate": "2025-10-15T23:00:00.000Z"
  },
  "extensions": {
    "1": {
      "formNumber": "4868",
      "formName": "Application for Automatic Extension...",
      "extensionType": "automatic",
      "filingRequired": true,
      "paymentRequired": true
    }
  }
}

Response (Multiple Variations)

{
  "formNumber": "05-158",
  "variationCount": 2,
  "results": [
    {
      "formNumber": "05-158",
      "formName": "Texas Franchise Tax Report - Short Form",
      "entityType": "corporation",
      "locality": "Texas",
      "localityType": "state",
      // ... dates and extensions
    },
    {
      "formNumber": "05-158",
      "formName": "Texas Franchise Tax Report - Short Form",
      "entityType": "scorp",
      "locality": "Texas",
      "localityType": "state",
      // ... dates and extensions
    }
  ],
  "message": "Multiple variations found. Each has different due dates based on entity type and locality."
}

Example Request

curl -X POST https://tax-forms.odov.io/api/v2/calculate-simple \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "formNumber": "1040", "coverageStartDate": "2024-01-01", "coverageEndDate": "2024-12-31" }'
const response = await fetch('https://tax-forms.odov.io/api/v2/calculate-simple', { method: 'POST', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, body: JSON.stringify({ formNumber: '1040', coverageStartDate: '2024-01-01', coverageEndDate: '2024-12-31' }) }); const data = await response.json(); console.log(data);
import requests response = requests.post( 'https://tax-forms.odov.io/api/v2/calculate-simple', headers={ 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, json={ 'formNumber': '1040', 'coverageStartDate': '2024-01-01', 'coverageEndDate': '2024-12-31' } ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v2/calculate-simple') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri) request['X-API-Key'] = 'your-api-key-here' request['Content-Type'] = 'application/json' request.body = { formNumber: '1040', coverageStartDate: '2024-01-01', coverageEndDate: '2024-12-31' }.to_json response = http.request(request) data = JSON.parse(response.body) puts data

Metadata Endpoints

Use these endpoints to discover available values for forms, entity types, and localities.

GET/api/v2/metadata/form-numbers

Get all available form numbers with their names and variation counts

Response

{
  "formNumbers": [
    {
      "formNumber": "1040",
      "formName": "U.S. Individual Income Tax Return",
      "variations": 1
    },
    {
      "formNumber": "05-158",
      "formName": "Texas Franchise Tax Report - Short Form",
      "variations": 2
    }
  ],
  "count": 29
}

Example Request

curl -X GET "https://tax-forms.odov.io/api/v2/metadata/form-numbers" \ -H "X-API-Key: your-api-key-here"
const response = await fetch('https://tax-forms.odov.io/api/v2/metadata/form-numbers', { headers: { 'X-API-Key': 'your-api-key-here' } }); const data = await response.json(); console.log(data);
import requests response = requests.get( 'https://tax-forms.odov.io/api/v2/metadata/form-numbers', headers={'X-API-Key': 'your-api-key-here'} ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v2/metadata/form-numbers') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts data
GET/api/v2/metadata/entity-types

Get all entity types with display names and form counts

Response

{
  "entityTypes": [
    {
      "value": "individual",
      "displayName": "Individual",
      "formCount": 6
    },
    {
      "value": "corporation",
      "displayName": "Corporation",
      "formCount": 7
    }
  ],
  "count": 5
}

Example Request

curl -X GET "https://tax-forms.odov.io/api/v2/metadata/entity-types" \ -H "X-API-Key: your-api-key-here"
const response = await fetch('https://tax-forms.odov.io/api/v2/metadata/entity-types', { headers: { 'X-API-Key': 'your-api-key-here' } }); const data = await response.json(); console.log(data);
import requests response = requests.get( 'https://tax-forms.odov.io/api/v2/metadata/entity-types', headers={'X-API-Key': 'your-api-key-here'} ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v2/metadata/entity-types') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts data
GET/api/v2/metadata/localities

Get all localities with their types and form counts

Query Parameters

ParameterTypeDescription
localityTypestringFilter by locality type (federal, state, city)

Response

{
  "localities": [
    {
      "locality": "United States",
      "localityType": "federal",
      "formCount": 15
    },
    {
      "locality": "California",
      "localityType": "state",
      "formCount": 8
    }
  ],
  "count": 10
}

Example Request

curl -X GET "https://tax-forms.odov.io/api/v2/metadata/localities?localityType=state" \ -H "X-API-Key: your-api-key-here"
const response = await fetch('https://tax-forms.odov.io/api/v2/metadata/localities?localityType=state', { headers: { 'X-API-Key': 'your-api-key-here' } }); const data = await response.json(); console.log(data);
import requests response = requests.get( 'https://tax-forms.odov.io/api/v2/metadata/localities', headers={'X-API-Key': 'your-api-key-here'}, params={'localityType': 'state'} ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v2/metadata/localities') uri.query = URI.encode_www_form({localityType: 'state'}) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts data
GET/api/v2/metadata/locality-types

Get all locality types with display names and form counts

Response

{
  "localityTypes": [
    {
      "value": "federal",
      "displayName": "Federal",
      "formCount": 15
    },
    {
      "value": "state",
      "displayName": "State",
      "formCount": 12
    },
    {
      "value": "city",
      "displayName": "City/Local",
      "formCount": 2
    }
  ],
  "count": 3
}

Example Request

curl -X GET "https://tax-forms.odov.io/api/v2/metadata/locality-types" \ -H "X-API-Key: your-api-key-here"
const response = await fetch('https://tax-forms.odov.io/api/v2/metadata/locality-types', { headers: { 'X-API-Key': 'your-api-key-here' } }); const data = await response.json(); console.log(data);
import requests response = requests.get( 'https://tax-forms.odov.io/api/v2/metadata/locality-types', headers={'X-API-Key': 'your-api-key-here'} ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v2/metadata/locality-types') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts data

API Version 1Legacy

Note: Version 1 is maintained for backwards compatibility. New integrations should use Version 2 for access to historical data and year-specific rules.

POST/api/v1/calculate

Calculate due dates (returns single date, no historical data)

Request Body

{
  "formNumber": "1040",
  "entityType": "individual",
  "localityType": "federal",
  "locality": "United States",
  "coverageStartDate": "2024-01-01",
  "coverageEndDate": "2024-12-31"
}

Response

{
  "formNumber": "1040",
  "formName": "U.S. Individual Income Tax Return",
  "calculationBase": "end",
  "dates": {
    "dueDate": "2025-04-15T23:00:00.000Z",
    "extensionDueDate": "2025-10-15T23:00:00.000Z"
  },
  "extension": {
    "formNumber": "4868",
    "formName": "Application for Automatic Extension of Time to File U.S. Individual Income Tax Return",
    "piggybackFed": false
  }
}

Example Request

curl -X POST https://tax-forms.odov.io/api/v1/calculate \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "formNumber": "1040", "entityType": "individual", "localityType": "federal", "locality": "United States", "coverageStartDate": "2024-01-01", "coverageEndDate": "2024-12-31" }'
const response = await fetch('https://tax-forms.odov.io/api/v1/calculate', { method: 'POST', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, body: JSON.stringify({ formNumber: '1040', entityType: 'individual', localityType: 'federal', locality: 'United States', coverageStartDate: '2024-01-01', coverageEndDate: '2024-12-31' }) }); const data = await response.json(); console.log(data);
import requests response = requests.post( 'https://tax-forms.odov.io/api/v1/calculate', headers={ 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, json={ 'formNumber': '1040', 'entityType': 'individual', 'localityType': 'federal', 'locality': 'United States', 'coverageStartDate': '2024-01-01', 'coverageEndDate': '2024-12-31' } ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v1/calculate') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri) request['X-API-Key'] = 'your-api-key-here' request['Content-Type'] = 'application/json' request.body = { formNumber: '1040', entityType: 'individual', localityType: 'federal', locality: 'United States', coverageStartDate: '2024-01-01', coverageEndDate: '2024-12-31' }.to_json response = http.request(request) data = JSON.parse(response.body) puts data

Key Differences from V2

  • Returns only the most recent calculation rule
  • No year-specific variations
  • Simpler response structure
  • No parent form relationships
GET/api/v1/forms

List all available forms (simplified structure)

Query Parameters

ParameterTypeDescription
entityTypestringFilter by entity type
localityTypestringFilter by locality type
localitystringFilter by specific locality
sincestringISO 8601 date - get forms modified since this date

Response

{
  "forms": [
    {
      "formNumber": "1040",
      "formName": "U.S. Individual Income Tax Return",
      "localityType": "federal",
      "locality": "United States",
      "entityType": "individual",
      "parentFormNumbers": ["1040"],
      "owner": "ODOV",
      "calculationBase": "end",
      "calculationRules": [
        {
          "effectiveYears": [2019, 2021, 2022, 2023, 2024],
          "dueDate": {
            "monthsAfterCalculationBase": 4,
            "dayOfMonth": 15
          },
          "extensions": {
            "1": {
              "formNumber": "4868",
              "formName": "Application for Automatic Extension of Time to File U.S. Individual Income Tax Return",
              "monthsAfterCalculationBase": 10,
              "dayOfMonth": 15,
              "extensionType": "automatic"
            }
          }
        }
      ]
    }
  ],
  "lastUpdated": "2024-01-15T10:30:00Z",
  "version": "1.0",
  "count": 150
}

Example Request

curl -X GET "https://tax-forms.odov.io/api/v1/forms?entityType=individual" \ -H "X-API-Key: your-api-key-here"
const response = await fetch('https://tax-forms.odov.io/api/v1/forms?entityType=individual', { headers: { 'X-API-Key': 'your-api-key-here' } }); const data = await response.json(); console.log(data);
import requests response = requests.get( 'https://tax-forms.odov.io/api/v1/forms', headers={'X-API-Key': 'your-api-key-here'}, params={'entityType': 'individual'} ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v1/forms') uri.query = URI.encode_www_form({entityType: 'individual'}) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts data
GET/api/v1/forms/{formNumber}

Get form details by form number

Response

{
  "formNumber": "1040",
  "variations": [
    {
      "formNumber": "1040",
      "formName": "U.S. Individual Income Tax Return",
      "entityType": "individual",
      "localityType": "federal",
      "locality": "United States"
    }
  ],
  "count": 1
}

Example Request

curl -X GET "https://tax-forms.odov.io/api/v1/forms/1040" \ -H "X-API-Key: your-api-key-here"
const response = await fetch('https://tax-forms.odov.io/api/v1/forms/1040', { headers: { 'X-API-Key': 'your-api-key-here' } }); const data = await response.json(); console.log(data);
import requests response = requests.get( 'https://tax-forms.odov.io/api/v1/forms/1040', headers={'X-API-Key': 'your-api-key-here'} ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v1/forms/1040') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['X-API-Key'] = 'your-api-key-here' response = http.request(request) data = JSON.parse(response.body) puts data
POST/api/v1/batch

Calculate multiple forms in one request

Request Body

{
  "calculations": [
    {
      "formNumber": "1040",
      "entityType": "individual",
      "localityType": "federal",
      "locality": "United States",
      "coverageStartDate": "2024-01-01",
      "coverageEndDate": "2024-12-31"
    },
    {
      "formNumber": "941",
      "entityType": "business",
      "localityType": "federal",
      "locality": "United States",
      "coverageStartDate": "2024-01-01",
      "coverageEndDate": "2024-03-31"
    }
  ]
}

Response

{
  "results": [
    {
      "formNumber": "1040",
      "entityType": "individual",
      "localityType": "federal",
      "locality": "United States",
      "coverageStartDate": "2024-01-01",
      "coverageEndDate": "2024-12-31",
      "success": true,
      "dueDate": "2025-04-15",
      "extensionDueDate": "2025-10-15"
    }
  ],
  "summary": {
    "total": 1,
    "successful": 1,
    "failed": 0
  }
}

Example Request

curl -X POST https://tax-forms.odov.io/api/v1/batch \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "calculations": [ { "formNumber": "1040", "entityType": "individual", "localityType": "federal", "locality": "United States", "coverageStartDate": "2024-01-01", "coverageEndDate": "2024-12-31" } ] }'
const response = await fetch('https://tax-forms.odov.io/api/v1/batch', { method: 'POST', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, body: JSON.stringify({ calculations: [ { formNumber: '1040', entityType: 'individual', localityType: 'federal', locality: 'United States', coverageStartDate: '2024-01-01', coverageEndDate: '2024-12-31' } ] }) }); const data = await response.json(); console.log(data);
import requests response = requests.post( 'https://tax-forms.odov.io/api/v1/batch', headers={ 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, json={ 'calculations': [ { 'formNumber': '1040', 'entityType': 'individual', 'localityType': 'federal', 'locality': 'United States', 'coverageStartDate': '2024-01-01', 'coverageEndDate': '2024-12-31' } ] } ) data = response.json() print(data)
require 'net/http' require 'json' uri = URI('https://tax-forms.odov.io/api/v1/batch') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri) request['X-API-Key'] = 'your-api-key-here' request['Content-Type'] = 'application/json' request.body = { calculations: [ { formNumber: '1040', entityType: 'individual', localityType: 'federal', locality: 'United States', coverageStartDate: '2024-01-01', coverageEndDate: '2024-12-31' } ] }.to_json response = http.request(request) data = JSON.parse(response.body) puts data

Webhooks

Configure webhooks to receive automated notifications when tax forms are updated or added. Perfect for keeping your systems in sync without constant polling.

Setting Up Webhooks

  1. Add your webhook URL in the API Key Management dashboard
  2. Your endpoint must use HTTPS and respond within 30 seconds
  3. Webhooks are triggered daily for forms modified since your last sync
  4. Each webhook includes only the forms that have changed

Webhook Payload

When forms are updated, we'll send a POST request to your webhook URL:

POST https://your-webhook-url.com/endpoint Content-Type: application/json X-ODOV-Event: forms.sync X-ODOV-API-Key: odov_k3y... { "event": "forms.sync", "timestamp": "2024-03-15T14:30:00Z", "data": { "changedForms": [ { "formNumber": "1040", "formName": "U.S. Individual Income Tax Return", "entityType": "individual", "locality": "United States", "localityType": "federal", "lastModified": "2024-03-15T10:00:00Z" } ], "changesSince": "2024-03-14T14:30:00Z", "totalChanges": 1 } }

Webhook Headers

HeaderDescription
X-ODOV-EventEvent type (currently only "forms.sync")
X-ODOV-API-KeyPartial API key for identification

Responding to Webhooks

Your endpoint should return a 2xx status code to acknowledge receipt. Any other status code or timeout will be considered a failure.

Example Webhook Handler

# This is what ODOV sends to your webhook: curl -X POST https://your-webhook-url.com/endpoint \ -H "Content-Type: application/json" \ -H "X-ODOV-Event: forms.sync" \ -H "X-ODOV-API-Key: odov_k3y..." \ -d '{ "event": "forms.sync", "timestamp": "2024-03-15T14:30:00Z", "data": { "changedForms": [...], "changesSince": "2024-03-14T14:30:00Z", "totalChanges": 1 } }'
// Express.js webhook handler app.post('/webhook/odov', (req, res) => { const event = req.headers['x-odov-event']; const apiKeyPrefix = req.headers['x-odov-api-key']; if (event === 'forms.sync') { const { changedForms, changesSince } = req.body.data; // Process the changed forms changedForms.forEach(form => { console.log(`Form ${form.formNumber} was updated`); // Update your database, trigger workflows, etc. }); // Acknowledge receipt res.status(200).json({ received: true }); } else { res.status(400).json({ error: 'Unknown event type' }); } });
# Flask webhook handler from flask import Flask, request, jsonify @app.route('/webhook/odov', methods=['POST']) def handle_odov_webhook(): event = request.headers.get('X-ODOV-Event') api_key_prefix = request.headers.get('X-ODOV-API-Key') if event == 'forms.sync': data = request.json['data'] changed_forms = data['changedForms'] # Process the changed forms for form in changed_forms: print(f"Form {form['formNumber']} was updated") # Update your database, trigger workflows, etc. # Acknowledge receipt return jsonify({'received': True}), 200 else: return jsonify({'error': 'Unknown event type'}), 400
# Sinatra webhook handler require 'sinatra' require 'json' post '/webhook/odov' do request.body.rewind payload = JSON.parse(request.body.read) event = request.env['HTTP_X_ODOV_EVENT'] api_key_prefix = request.env['HTTP_X_ODOV_API_KEY'] if event == 'forms.sync' changed_forms = payload['data']['changedForms'] # Process the changed forms changed_forms.each do |form| puts "Form #{form['formNumber']} was updated" # Update your database, trigger workflows, etc. end # Acknowledge receipt status 200 { received: true }.to_json else status 400 { error: 'Unknown event type' }.to_json end end
💡 Best Practices
  • Process webhooks asynchronously to respond quickly
  • Store the raw payload for debugging and replay capability
  • Use the changesSince timestamp to detect gaps
  • Implement retry logic in case your processing fails
  • Monitor webhook failures in your API dashboard

Error Responses

The API uses standard HTTP status codes and returns detailed error information:

{
  "error": "Validation failed",
  "details": {
    "formNumber": "Form not found",
    "entityType": "Invalid entity type"
  }
}

Common Status Codes

Status CodeDescription
200Request successful
400Bad request - Invalid parameters
401Unauthorized - Invalid or missing API key
404Not found - Form or resource doesn't exist
429Too many requests - Rate limit exceeded
500Internal server error