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

Working with Due Dates

Due dates in our database represent statutory deadlines — the date as written in the law or tax authority instructions. When a statutory date falls on a weekend or federal holiday, the actual filing deadline shifts to the next business day.

If you use the /api/v2/calculate endpoint, this adjustment is handled for you automatically. If you consume form data directly from the /api/v2/forms endpoint and compute dates yourself, you will need to apply weekend and holiday adjustment logic to each due date and extension date.

Below are ready-to-use implementations you can drop into your application.

# No client-side logic needed if you use /api/v2/calculate — # the response dates already account for weekends and holidays. # # This section provides helper code for consumers who read # form rules directly from /api/v2/forms and compute dates # in their own application. See the JavaScript, Python, or # Ruby tabs for copy-paste implementations.
/** * Adjust a statutory due date to the next business day. * Handles weekends and U.S. federal holidays. * * @param {Date} date - The statutory due date * @returns {Date} - The adjusted filing deadline */ function adjustToBusinessDay(date) { const d = new Date(date); while (isWeekend(d) || isFederalHoliday(d)) { d.setDate(d.getDate() + 1); } return d; } function isWeekend(date) { const day = date.getDay(); return day === 0 || day === 6; } function isFederalHoliday(date) { const m = date.getMonth(); // 0-indexed const d = date.getDate(); const dow = date.getDay(); if (m === 0 && d === 1) return true; // New Year's Day if (m === 0 && dow === 1 && d >= 15 && d <= 21) return true; // MLK Day if (m === 1 && dow === 1 && d >= 15 && d <= 21) return true; // Presidents' Day if (m === 4 && dow === 1 && d >= 25) return true; // Memorial Day if (m === 5 && d === 19) return true; // Juneteenth if (m === 6 && d === 4) return true; // Independence Day if (m === 8 && dow === 1 && d <= 7) return true; // Labor Day if (m === 9 && dow === 1 && d >= 8 && d <= 14) return true; // Columbus Day if (m === 10 && d === 11) return true; // Veterans Day if (m === 10 && dow === 4 && d >= 22 && d <= 28) return true; // Thanksgiving if (m === 11 && d === 25) return true; // Christmas Day return false; } // Example: compute the actual due date from a calculation rule function computeDueDate(rule, coverageEndDate) { const base = new Date(coverageEndDate); base.setMonth(base.getMonth() + rule.dueDate.monthsAfterCalculationBase); base.setDate(rule.dueDate.dayOfMonth); return adjustToBusinessDay(base); }
from datetime import date, timedelta def adjust_to_business_day(d: date) -> date: """Adjust a statutory due date to the next business day.""" while is_weekend(d) or is_federal_holiday(d): d += timedelta(days=1) return d def is_weekend(d: date) -> bool: return d.weekday() >= 5 # Saturday=5, Sunday=6 def is_federal_holiday(d: date) -> bool: m, day, dow = d.month, d.day, d.weekday() # Monday=0 if m == 1 and day == 1: return True # New Year's Day if m == 1 and dow == 0 and 15 <= day <= 21: # MLK Day return True if m == 2 and dow == 0 and 15 <= day <= 21: # Presidents' Day return True if m == 5 and dow == 0 and day >= 25: # Memorial Day return True if m == 6 and day == 19: return True # Juneteenth if m == 7 and day == 4: return True # Independence Day if m == 9 and dow == 0 and day <= 7: # Labor Day return True if m == 10 and dow == 0 and 8 <= day <= 14: # Columbus Day return True if m == 11 and day == 11: return True # Veterans Day if m == 11 and dow == 3 and 22 <= day <= 28: # Thanksgiving return True if m == 12 and day == 25: return True # Christmas Day return False # Example: compute the actual due date from a calculation rule def compute_due_date(rule, coverage_end: date) -> date: months = rule["dueDate"]["monthsAfterCalculationBase"] day_of_month = rule["dueDate"]["dayOfMonth"] year = coverage_end.year + (coverage_end.month + months - 1) // 12 month = (coverage_end.month + months - 1) % 12 + 1 return adjust_to_business_day(date(year, month, day_of_month))
require 'date' def adjust_to_business_day(d) d += 1 while weekend?(d) || federal_holiday?(d) d end def weekend?(d) d.saturday? || d.sunday? end def federal_holiday?(d) m, day, dow = d.month, d.day, d.wday # Sunday=0, Monday=1 return true if m == 1 && day == 1 # New Year's Day return true if m == 1 && dow == 1 && (15..21).include?(day) # MLK Day return true if m == 2 && dow == 1 && (15..21).include?(day) # Presidents' Day return true if m == 5 && dow == 1 && day >= 25 # Memorial Day return true if m == 6 && day == 19 # Juneteenth return true if m == 7 && day == 4 # Independence Day return true if m == 9 && dow == 1 && day <= 7 # Labor Day return true if m == 10 && dow == 1 && (8..14).include?(day) # Columbus Day return true if m == 11 && day == 11 # Veterans Day return true if m == 11 && dow == 4 && (22..28).include?(day) # Thanksgiving return true if m == 12 && day == 25 # Christmas Day false end # Example: compute the actual due date from a calculation rule def compute_due_date(rule, coverage_end) months = rule[:dueDate][:monthsAfterCalculationBase] day_of_month = rule[:dueDate][:dayOfMonth] target = coverage_end >> months # Date#>> advances by months target = Date.new(target.year, target.month, day_of_month) adjust_to_business_day(target) end

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