Ohio Sales Tax Calculator

Get Sales Tax Rate

Calculates the exact Ohio combined sales tax rate and statutory components for any jurisdiction within the state of Ohio.

Query Parameters

countystringquery

The statutory Ohio county name or slug (e.g. warren, franklin, cuyahoga). If provided, resolves immediately to that county's rate.

zipstringquery

5-digit Ohio postal ZIP code (e.g. 43001 or 43003). Unambiguous ZIPs return the exact rate immediately. Ambiguous multi-county ZIPs return structured candidate counties or require the address parameter.

addressstringquery

Full delivery street address (e.g. 100 E Main St, Sunbury, OH 43003). Automatically geocoded via US Census Bureau boundaries to resolve split-county ZIPs with statutory precision.

datestringquery

Evaluation date in YYYY-MM-DD ISO format (defaults to current date). Used for historical audit calculations and future rate adjustments.


Code Examples

cURL
curl -X GET "https://app.ohiosalestaxcalculator.com/api/v1/rates?county=franklin" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
TypeScript / Node.js
const res = await fetch("https://app.ohiosalestaxcalculator.com/api/v1/rates?county=franklin", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Accept": "application/json"
  }
});
const rateData = await res.json();
console.log(`Franklin Rate: ${(rateData.combined_rate * 100).toFixed(2)}%`);
Python
import requests

res = requests.get(
    "https://app.ohiosalestaxcalculator.com/api/v1/rates",
    params={"county": "franklin"},
    headers={"Authorization": "Bearer YOUR_API_KEY"}
)
print(res.json())

Response: Exact Rate Match (200 OK)

countystringrequired

Canonical name of the Ohio county (e.g. Warren, Franklin).

fipsstringrequired

5-digit Ohio County FIPS code (e.g. 39165 for Warren, 39049 for Franklin).

combined_ratenumberrequired

Total statutory sales tax decimal rate (e.g. 0.08 for 8.00%, 0.0725 for 7.25%).

componentsobjectrequired
Rate Components
statenumber
Ohio State baseline rate (0.0575 / 5.75%).
county_permissivenumber
County permissive tax rate (0.0050 - 0.0150).
transit_authoritynumber
Regional transit authority levy (e.g. COTA 0.0100).
effective_fromstring | null

Effective date of the active rate (YYYY-MM-DD). null when the start date of the current rate is not recorded in our change history; treat null as "in effect, start date unknown", not as an error.

transit_district_possibleboolean

true when the county is only partly inside a transit district (Delaware, Fairfield, Licking and Union: COTA; Wood: TARTA) and the request did not let us tell which side the location is on. combined_rate is then the countywide rate, and the rate inside the district is transit_district.combined_rate_inside (for example 8.25% in the COTA part of Licking County). Pass address to resolve it. County and ZIP lookups in these counties always return true.

transit_districtobject | null

null unless the county is split by a transit district. Fields: authority (COTA or TARTA), name, area, combined_rate_inside, status (inside, outside or unknown), and note. For address lookups, status comes from the Census incorporated place returned for the address (for example Columbus, Dublin, Westerville or Reynoldsburg for COTA). When the address is inside, the levy is included in combined_rate and components.transit_authority. A city annexation the Census has not yet recorded can be misclassified, so check status for addresses near a city line. Franklin County lies entirely within COTA, so its 1.00% COTA levy always appears in components.transit_authority and transit_district is null.

has_upcoming_changeboolean

true if an upcoming rate adjustment has been enacted by county commissioners with ODT.

resolved_bystring

Method used to resolve jurisdiction: county_param, zip_unambiguous, address_geocoding, or zip_fallback. zip_fallback means the Census geocoder could not match the street address (common for addresses like "1 Courthouse Square"), so the ZIP code from the zip parameter or the end of the address was used. If that ZIP spans several counties you get the ambiguous response instead. Include the ZIP in every address lookup so this fallback is available.

ambiguousboolean

Always false for exact matches.

statutory_sourcingstring

Legal sourcing citation under Ohio R.C. § 5739.033.

request_idstring

Unique request identifier for audit defense logging.

Response Example (200 OK)

{
  "county": "Franklin",
  "fips": "39049",
  "date_evaluated": "2026-10-03",
  "combined_rate": 0.08,
  "components": {
    "state": 0.0575,
    "county_permissive": 0.0125,
    "transit_authority": 0.01
  },
  "effective_from": "2025-04-01",
  "transit_district_possible": false,
  "transit_district": null,
  "has_upcoming_change": false,
  "upcoming": null,
  "source": "Ohio Department of Taxation",
  "last_reconciled": "2026-10-01",
  "jurisdiction_type": "County Permissive",
  "statutory_sourcing": "Origin-Sourced under Ohio R.C. 5739.033(B)(1) for In-State Vendors",
  "resolved_by": "county_param",
  "ambiguous": false,
  "request_id": "req_8f12_90ab"
}

Response: Ambiguous Multi-County ZIP (200 OK)

When querying a split ZIP without a street address:

{
  "ambiguous": true,
  "zip": "43003",
  "message": "ZIP code 43003 spans 2 Ohio counties (Delaware, Morrow). Provide the 'address' query parameter for exact address-level geocoding under Ohio R.C. 5739.033, or specify 'county'.",
  "candidate_counties": [
    {
      "county": "Delaware",
      "fips": "39041",
      "combined_rate": 0.07,
      "components": { "state": 0.0575, "county_permissive": 0.0125, "transit_authority": 0.0 },
      "effective_from": null,
      "transit_district_possible": true,
      "transit_district": {
        "authority": "COTA",
        "name": "Central Ohio Transit Authority",
        "area": "the parts of Columbus, Dublin and Westerville that cross the county line",
        "combined_rate_inside": 0.08,
        "status": "unknown",
        "note": "Part of Delaware County is in the COTA district (the parts of Columbus, Dublin and Westerville that cross the county line), where the rate is 8.00%. combined_rate is the countywide rate; pass 'address' to resolve it."
      }
    },
    {
      "county": "Morrow",
      "fips": "39117",
      "combined_rate": 0.0725,
      "components": { "state": 0.0575, "county_permissive": 0.015, "transit_authority": 0.0 },
      "effective_from": null,
      "transit_district_possible": false,
      "transit_district": null
    }
  ],
  "date_evaluated": "2026-10-03",
  "request_id": "req_1082_ef7c"
}

Error Responses (RFC 7807)

400 Bad Request

{
  "type": "https://ohiosalestaxcalculator.com/errors/bad-request",
  "title": "Invalid Parameters",
  "status": 400,
  "detail": "At least one of 'county', 'zip', or 'address' must be provided.",
  "request_id": "req_err_400"
}

401 Unauthorized

{
  "type": "https://ohiosalestaxcalculator.com/errors/unauthorized",
  "title": "API Key Required",
  "status": 401,
  "detail": "A valid Bearer API key starting with 'ostx_' is required.",
  "request_id": "req_err_401"
}

429 Too Many Requests

{
  "type": "https://ohiosalestaxcalculator.com/errors/rate-limit-exceeded",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit of 10 requests per second exceeded. Please back off and retry.",
  "request_id": "req_err_429"
}