How to Check a Restaurant Health Score via API Lookup

Whether you're building a food delivery app, a restaurant discovery platform, or a franchise monitoring tool, you'll eventually need to answer a simple question: what is this restaurant's current health inspection score? The challenge is that public health inspection data lives across thousands of county and city databases, each with its own format, grading scale, and update frequency.

This guide walks through the practical steps of looking up a restaurant's health score using the FoodSafe Score API - from crafting your first lookup request to handling edge cases like no-match results, ambiguous addresses, and when to fall back to a geographic search. We'll cover both JavaScript and Python implementations with working code samples you can drop straight into your project.

Understanding the Lookup Endpoint

The primary way to fetch a restaurant's health score is the name-and-address lookup endpoint. This takes a restaurant name, a street address, and a city, then returns a normalized 0-100 score along with the underlying inspection metadata pulled from the relevant jurisdiction's public records.

The endpoint signature looks like this:

GET https://api.foodsafescoreapi.com/v1/lookup
  ?name=Tacos El Primo
  &address=1234 Mission St
  &city=San Francisco
  &state=CA

The name, address, and city parameters are all required. The state parameter is strongly recommended whenever you have it - it helps the API route to the correct jurisdiction authority and resolve faster for cities that share names across state lines.

Authentication

All requests require your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

You can get an API key by joining the waitlist for early access. During beta, keys are issued on a rolling basis as new jurisdictions come online.

JavaScript Implementation (Fetch API)

Here is a complete JavaScript function that performs a restaurant lookup and handles the response. This works in both browser and Node.js (v18+) environments since native fetch is available in both.

const FOODSAFE_API_KEY = process.env.FOODSAFE_API_KEY;
const BASE_URL = 'https://api.foodsafescoreapi.com/v1';

async function getRestaurantHealthScore({ name, address, city, state }) {
  const params = new URLSearchParams({ name, address, city });
  if (state) params.set('state', state);

  const res = await fetch(`${BASE_URL}/lookup?${params}`, {
    headers: {
      'Authorization': `Bearer ${FOODSAFE_API_KEY}`,
      'Accept': 'application/json'
    }
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message || `Lookup failed (${res.status})`);
  }

  const data = await res.json();

  if (data.match_status === 'not_found') {
    return null; // Caller can fall back to geo search
  }

  return data;
}

// Usage
try {
  const result = await getRestaurantHealthScore({
    name: 'Tacos El Primo',
    address: '1234 Mission St',
    city: 'San Francisco',
    state: 'CA'
  });

  if (result) {
    console.log(`Score: ${result.score} (Grade ${result.grade})`);
    console.log(`Last inspected: ${result.last_inspection_date}`);
    console.log(`Violations: ${result.violation_count}`);
  } else {
    console.log('Restaurant not found - trying geo search');
  }
} catch (err) {
  console.error('Health score lookup failed:', err.message);
}

Notice the response.ok check before calling .json(). Skipping this is the single most common mistake when working with external APIs - without it, a 401 or 500 error response gets silently swallowed and you end up debugging phantom data issues instead of seeing the real error message.

Python Implementation (requests library)

The same lookup in Python using the requests library:

import os
import requests

FOODSAFE_API_KEY = os.environ['FOODSAFE_API_KEY']
BASE_URL = 'https://api.foodsafescoreapi.com/v1'

def get_restaurant_health_score(name, address, city, state=None):
    params = {'name': name, 'address': address, 'city': city}
    if state:
        params['state'] = state

    headers = {
        'Authorization': f'Bearer {FOODSAFE_API_KEY}',
        'Accept': 'application/json'
    }

    response = requests.get(f'{BASE_URL}/lookup', params=params, headers=headers)
    response.raise_for_status()  # Raises HTTPError for 4xx/5xx

    data = response.json()

    if data.get('match_status') == 'not_found':
        return None

    return data

# Usage
result = get_restaurant_health_score(
    name='Tacos El Primo',
    address='1234 Mission St',
    city='San Francisco',
    state='CA'
)

if result:
    print(f"Score: {result['score']} (Grade {result['grade']})")
    print(f"Last inspected: {result['last_inspection_date']}")
    print(f"Critical violations: {result['critical_violation_count']}")
    print(f"Non-critical violations: {result['non_critical_violation_count']}")
else:
    print("Not found - fall back to geo search")

Understanding the Response Fields

A successful lookup returns a JSON object with the following key fields:

Field Type Description
score integer Normalized 0-100 score. Higher is better.
grade string Letter grade: A (85-100), B (70-84), C (50-69), F (0-49)
last_inspection_date string (ISO 8601) Date of the most recent health inspection on record
violation_count integer Total violations found in the most recent inspection
critical_violation_count integer Number of critical violations (each deducts 25 points)
non_critical_violation_count integer Number of non-critical violations (each deducts 5 points)
corrected_on_site_count integer Violations corrected on site (each deducts only 2 points)
jurisdiction string The health authority source (e.g. "SF Department of Public Health")
match_status string "exact", "fuzzy", or "not_found"
source_url string Link to the original public record for attribution

The match_status Field

Pay close attention to match_status. An exact match means the name and address resolved cleanly to a single record. A fuzzy match means the API found a close result but the name or address didn't match character-for-character - common with chains that have slightly different registered names ("McDonald's" vs "McDonalds") or addresses with suite numbers formatted differently. A not_found status means no record could be located for that combination.

Pro Tip

When you get a fuzzy match, surface the matched_name and matched_address fields in your UI alongside the score so users can confirm it's the right establishment. This is especially important for high-stakes use cases like insurance underwriting or franchise auditing.

Handling No-Match Results

A not_found result doesn't necessarily mean the restaurant has no inspection records - it may mean the name or address you passed doesn't match the record in the jurisdiction's database closely enough. Before surfacing a "no data" message to your users, try these fallback strategies:

Strategy 1 - Try a shorter name

Chain restaurants often register under shortened names. "The Cheesecake Factory Restaurant" may be indexed as "Cheesecake Factory". Try stripping common prefixes ("The", "A", "An") and suffixes ("Restaurant", "Grill", "Bar & Grill") from the name.

function normalizeName(name) {
  return name
    .replace(/^(the|a|an)\s+/i, '')
    .replace(/\s+(restaurant|grill|bar\s*&\s*grill|cafe|diner|kitchen)$/i, '')
    .trim();
}

Strategy 2 - Fall back to geo search

If name normalization doesn't help, fall back to the geo search endpoint using a lat/lng coordinate derived from the address. This searches all inspection records within a given radius and returns the closest match by name similarity.

async function getScoreWithGeoFallback({ name, address, city, state, lat, lng }) {
  // First try direct lookup
  const direct = await getRestaurantHealthScore({ name, address, city, state })
    .catch(() => null);

  if (direct) return direct;

  // Fall back to geo search if we have coordinates
  if (lat && lng) {
    const params = new URLSearchParams({
      name,
      lat: lat.toString(),
      lng: lng.toString(),
      radius_meters: '100'
    });

    const res = await fetch(`${BASE_URL}/geo-search?${params}`, {
      headers: { 'Authorization': `Bearer ${FOODSAFE_API_KEY}` }
    });

    if (!res.ok) return null;

    const data = await res.json();
    return data.results?.[0] || null;
  }

  return null;
}

For a deeper dive into the geographic search endpoint and when to use it, see our guide on how to integrate a restaurant health inspection API end-to-end.

Displaying Health Scores in a Consumer UI

Once you have a score from the API, the presentation layer matters as much as the data itself. Research on food safety transparency suggests that users make faster trust decisions from letter grades than from numeric scores - so lead with the grade and use the score as supporting detail.

Recommended UI fields to display

Grade badge component (JavaScript)

function renderGradeBadge(score, grade) {
  const colors = {
    A: { bg: '#16a34a', text: '#ffffff' },
    B: { bg: '#d97706', text: '#ffffff' },
    C: { bg: '#ea580c', text: '#ffffff' },
    F: { bg: '#dc2626', text: '#ffffff' }
  };

  const color = colors[grade] || colors.F;

  return `
    <div class="health-grade-badge" style="
      display: inline-flex;
      flex-direction: column;
      align-items: center;
      background: ${color.bg};
      color: ${color.text};
      border-radius: 8px;
      padding: 0.5rem 0.85rem;
      font-weight: 800;
      min-width: 56px;
    ">
      <span style="font-size: 1.75rem; line-height: 1;">${grade}</span>
      <span style="font-size: 0.65rem; opacity: 0.85;">${score}/100</span>
    </div>
  `;
}

Inspection date freshness indicator

Always display how old the inspection data is - a date alone isn't intuitive for most users. A simple "inspected 8 months ago" is more immediately understood than a raw ISO date.

function inspectionAgeText(isoDate) {
  const inspectedAt = new Date(isoDate);
  const now = new Date();
  const months = Math.floor((now - inspectedAt) / (1000 * 60 * 60 * 24 * 30));

  if (months < 1) return 'Inspected this month';
  if (months === 1) return 'Inspected 1 month ago';
  if (months < 12) return `Inspected ${months} months ago`;

  const years = Math.floor(months / 12);
  return years === 1 ? 'Inspected 1 year ago' : `Inspected ${years} years ago`;
}

Rate Limits and Pricing

Each successful lookup call costs $0.25 or draws from your monthly plan's included lookups. To avoid surprise charges, always cache results on your end - inspection data typically updates monthly, so a 24-hour cache TTL is a reasonable default for most consumer applications. For more active monitoring scenarios (like franchise QA), you may want a shorter TTL or a webhook-based approach for change alerts.

Caching Recommendation

Store the last_inspection_date in your cache key logic. If the API returns a fresher inspection date than what you have cached, invalidate the cache immediately and update your stored record regardless of TTL.

Bulk Lookups

If you need to check scores for a large list of restaurants - say, all franchise locations in a city - use the bulk-by-zip endpoint rather than calling the lookup endpoint in a loop. It returns all inspection records in a given ZIP code in a single call, which you can then match against your location list client-side. See our post on how to normalize food safety scores across jurisdictions for the full bulk workflow.

Error Handling Reference

Here are the HTTP status codes you'll encounter and how to handle each:

Next Steps

With the lookup endpoint working, you have the foundation for a wide range of food safety features. Food delivery apps can surface inspection badges on restaurant cards. Franchise operators can build bulk monitoring pipelines. For coverage of those more advanced patterns - including how to structure a monitoring dashboard and how to set up score-change alerts - see our guides on integrating restaurant health scores into a food delivery platform and franchise health inspection monitoring.

The lookup endpoint is designed to be the simplest possible on-ramp. One HTTP call, one JSON response, one normalized score - regardless of which of the 3,000+ US jurisdictions the restaurant sits in. From there, you can layer in history, trends, and alerting as your use case demands.

Ready to Add Health Scores to Your Platform?

Join the FoodSafe Score API waitlist and get early access to normalized inspection data across 10+ major US jurisdictions.