Need API access or technical support?Contact us
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
All API requests must include your API key in the request header:
X-API-Key: your-api-key-hereRate Limiting: API keys are limited to 100 requests per 10 minutes. Contact support if you need higher limits.
/api/v2/calculateCalculate due dates for a specific tax form
{
"formNumber": "1040",
"entityType": "individual",
"locality": "United States",
"localityType": "federal",
"coverageStartDate": "2024-01-01",
"coverageEndDate": "2024-12-31"
}{
"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"
}
]
}
}
}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/api/v2/formsList all available forms with their calculation rules
| Parameter | Type | Description |
|---|---|---|
entityType | string | Filter by entity type (individual, corporation, etc.) |
localityType | string | Filter by locality type (federal, state, city) |
locality | string | Filter by specific locality (e.g., "California") |
since | string | ISO 8601 date - Get only forms added/modified after this date |
{
"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
}
}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/api/v2/forms?since={ISO-8601-date}Track forms that have been added or modified after a specific date
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.
| Parameter | Required | Description |
|---|---|---|
since | Yes | ISO 8601 date (e.g., 2024-01-01T00:00:00Z) |
entityType | No | Filter by entity type |
localityType | No | Filter by locality type |
{
"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"
}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"lastUpdated timestamp from each responsesince parameter in your next request/api/v2/forms/{formNumber}Get detailed information about a specific form
| Parameter | Required | Description |
|---|---|---|
entityType | Sometimes | Required if form exists for multiple entity types |
locality | Sometimes | Required for state/local forms |
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/api/v2/calculate-simpleNew!Calculate due dates with just a form number - returns all variations
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.
{
"formNumber": "1040",
"coverageStartDate": "2024-01-01",
"coverageEndDate": "2024-12-31"
}{
"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
}
}
}{
"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."
}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 dataUse these endpoints to discover available values for forms, entity types, and localities.
/api/v2/metadata/form-numbersGet all available form numbers with their names and variation counts
{
"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
}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/api/v2/metadata/entity-typesGet all entity types with display names and form counts
{
"entityTypes": [
{
"value": "individual",
"displayName": "Individual",
"formCount": 6
},
{
"value": "corporation",
"displayName": "Corporation",
"formCount": 7
}
],
"count": 5
}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/api/v2/metadata/localitiesGet all localities with their types and form counts
| Parameter | Type | Description |
|---|---|---|
localityType | string | Filter by locality type (federal, state, city) |
{
"localities": [
{
"locality": "United States",
"localityType": "federal",
"formCount": 15
},
{
"locality": "California",
"localityType": "state",
"formCount": 8
}
],
"count": 10
}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/api/v2/metadata/locality-typesGet all locality types with display names and form counts
{
"localityTypes": [
{
"value": "federal",
"displayName": "Federal",
"formCount": 15
},
{
"value": "state",
"displayName": "State",
"formCount": 12
},
{
"value": "city",
"displayName": "City/Local",
"formCount": 2
}
],
"count": 3
}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 dataDue 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)
endNote: Version 1 is maintained for backwards compatibility. New integrations should use Version 2 for access to historical data and year-specific rules.
/api/v1/calculateCalculate due dates (returns single date, no historical data)
{
"formNumber": "1040",
"entityType": "individual",
"localityType": "federal",
"locality": "United States",
"coverageStartDate": "2024-01-01",
"coverageEndDate": "2024-12-31"
}{
"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
}
}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/api/v1/formsList all available forms (simplified structure)
| Parameter | Type | Description |
|---|---|---|
entityType | string | Filter by entity type |
localityType | string | Filter by locality type |
locality | string | Filter by specific locality |
since | string | ISO 8601 date - get forms modified since this date |
{
"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
}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/api/v1/forms/{formNumber}Get form details by form number
{
"formNumber": "1040",
"variations": [
{
"formNumber": "1040",
"formName": "U.S. Individual Income Tax Return",
"entityType": "individual",
"localityType": "federal",
"locality": "United States"
}
],
"count": 1
}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/api/v1/batchCalculate multiple forms in one request
{
"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"
}
]
}{
"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
}
}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 dataConfigure webhooks to receive automated notifications when tax forms are updated or added. Perfect for keeping your systems in sync without constant polling.
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
}
}| Header | Description |
|---|---|
X-ODOV-Event | Event type (currently only "forms.sync") |
X-ODOV-API-Key | Partial API key for identification |
Your endpoint should return a 2xx status code to acknowledge receipt. Any other status code or timeout will be considered a failure.
# 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
endchangesSince timestamp to detect gapsThe API uses standard HTTP status codes and returns detailed error information:
{
"error": "Validation failed",
"details": {
"formNumber": "Form not found",
"entityType": "Invalid entity type"
}
}| Status Code | Description |
|---|---|
200 | Request successful |
400 | Bad request - Invalid parameters |
401 | Unauthorized - Invalid or missing API key |
404 | Not found - Form or resource doesn't exist |
429 | Too many requests - Rate limit exceeded |
500 | Internal server error |