Ohio Sales Tax Calculator

Quickstart Guide

This guide walks you through obtaining your API credentials and making your first live statutory tax rate lookup with the Ohio Sales Tax API.

  1. Obtain your Secret API Key

    Log into your Ohio Sales Tax Platform workspace:

    1. Navigate to API Keys & Webhooks.
    2. Click Create New API Key and provide a descriptive label (e.g., Production Shopify POS).
    3. Copy your secret API key immediately:
      ostx_live_9f8e7d6c5b4a3210...
      
  2. Choose your Lookup Strategy

    The API supports three lookup strategies depending on the data available at checkout:

    StrategyQuery ParameterRecommended Use Case
    County?county=franklinOver-the-counter POS where the county is fixed.
    5-Digit ZIP?zip=43001Express checkout when only a ZIP code is known.
    Street Address?address=100+E+Main+St...Deliveries and resolving ambiguous multi-county ZIPs.
  3. Send your First Rate Request

    Execute a rate lookup for Franklin County (Columbus, OH):

    cURL
    curl -X GET "https://app.ohiosalestaxcalculator.com/api/v1/rates?county=franklin" \
      -H "Authorization: Bearer YOUR_SECRET_KEY" \
      -H "Accept: application/json"
    
    TypeScript / Node.js
    const API_KEY = process.env.OHIO_SALES_TAX_API_KEY!;
    
    async function getFranklinTaxRate() {
      const response = await fetch(
        "https://app.ohiosalestaxcalculator.com/api/v1/rates?county=franklin",
        {
          headers: {
            "Authorization": `Bearer ${API_KEY}`,
            "Accept": "application/json",
          },
        }
      );
    
      if (!response.ok) {
        throw new Error(`API error: ${response.status}`);
      }
    
      const data = await response.json();
      console.log(`Franklin Combined Rate: ${(data.combined_rate * 100).toFixed(2)}%`);
      console.log("Levy Breakdown:", data.components);
      return data;
    }
    
    Python
    import os
    import requests
    
    api_key = os.environ.get("OHIO_SALES_TAX_API_KEY")
    
    response = requests.get(
        "https://app.ohiosalestaxcalculator.com/api/v1/rates",
        params={"county": "franklin"},
        headers={"Authorization": f"Bearer {api_key}"}
    )
    
    data = response.json()
    print(f"Franklin Combined Rate: {data['combined_rate'] * 100:.2f}%")
    print("Components:", data["components"])
    
    Go
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"net/http"
    	"os"
    )
    
    type RateResponse struct {
    	County       string  `json:"county"`
    	CombinedRate float64 `json:"combined_rate"`
    	Components   struct {
    		State            float64 `json:"state"`
    		CountyPermissive float64 `json:"county_permissive"`
    		TransitAuthority float64 `json:"transit_authority"`
    	} `json:"components"`
    }
    
    func main() {
    	apiKey := os.Getenv("OHIO_SALES_TAX_API_KEY")
    	req, _ := http.NewRequest("GET", "https://app.ohiosalestaxcalculator.com/api/v1/rates?county=franklin", nil)
    	req.Header.Set("Authorization", "Bearer "+apiKey)
    
    	resp, err := http.DefaultClient.Do(req)
    	if err != nil {
    		panic(err)
    	}
    	defer resp.Body.Close()
    
    	var result RateResponse
    	json.NewDecoder(resp.Body).Decode(&result)
    	fmt.Printf("Franklin Combined Rate: %.2f%%\n", result.CombinedRate*100)
    }
    
    PHP
    <?php
    $apiKey = getenv('OHIO_SALES_TAX_API_KEY');
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://app.ohiosalestaxcalculator.com/api/v1/rates?county=franklin");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        "Authorization: Bearer " . $apiKey,
        "Accept: application/json"
    ]);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    $data = json_decode($response, true);
    echo "Franklin Combined Rate: " . ($data['combined_rate'] * 100) . "%\n";
    ?>
    
  4. Inspect the Statutory Response

    The API returns a statutory response containing the exact decimal rate and explicit levy components:

    {
      "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",
      "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_88f91c_29a1"
    }
    

Handling Advanced Scenarios

1. Multi-County Split ZIP Codes

Over 530 ZIP codes in Ohio straddle county lines. When querying an ambiguous ZIP (e.g. 43003 which spans Delaware and Morrow counties):

curl -X GET "https://app.ohiosalestaxcalculator.com/api/v1/rates?zip=43003" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"

The API returns ambiguous: true along with candidate counties:

{
  "zip": "43003",
  "ambiguous": true,
  "message": "ZIP 43003 spans multiple Ohio counties. Provide physical street address to resolve exact boundary.",
  "candidate_counties": [
    { "county": "Delaware", "fips": "39041", "combined_rate": 0.07 },
    { "county": "Morrow", "fips": "39117", "combined_rate": 0.0725 }
  ],
  "request_id": "req_split_43003"
}

To resolve this unambiguously, provide the street address:

curl -X GET "https://app.ohiosalestaxcalculator.com/api/v1/rates?address=100+E+Main+St,+Sunbury,+OH+43003" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"

2. Historical & Future Date Evaluation

Tax audits frequently require recalculating past transactions. Similarly, county commissioners announce rate adjustments months in advance. Pass the date parameter in YYYY-MM-DD ISO format:

# Evaluate historical rate for Cuyahoga County in 2023
curl -X GET "https://app.ohiosalestaxcalculator.com/api/v1/rates?county=cuyahoga&date=2023-06-15" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"

Next Steps