PRūF Data API
Restaurant Data API documentation
Connect ingredient lists, allergen disclosures and nutrition to restaurant menus. Explore one restaurant or download a complete data release.
Connect your AI tools with MCP
The PRūF MCP server lets compatible AI assistants search restaurant menus, open item evidence and work with your subscribed data. Your Intelligence subscription includes ingredient comparisons, all supporting records and JSON or CSV research exports.
The connector runs on your computer over MCP’s standard-input/output transport. Install Node.js 22 or newer, then add the configuration below to a client that supports local MCP servers. It uses your API key and the same subscription permissions as the REST API.
Download the versioned connector · SHA-256 checksum. The launcher installs this archive automatically. It contains the connector and pinned dependency information.
{
"mcpServers": {
"pruf": {
"command": "npx",
"args": [
"--yes",
"--ignore-scripts",
"--package=https://www.prufapp.com/downloads/pruf-research-mcp-0.2.0.tgz",
"pruf-research-mcp"
],
"env": {
"PRUF_API_KEY": "YOUR_PRUF_API_KEY",
"PRUF_EXPORT_DIR": "/absolute/path/to/exports"
}
}
}
}- Keep your API key in the client’s private environment settings. Set
PRUF_EXPORT_DIRto an existing absolute folder path on your computer, then enable or restart the connector in your client. - Ask for the current release and its restaurant coverage, then search items or open their ingredient evidence.
- For Intelligence comparisons, select an ingredient and keep the same release throughout the analysis. Exports include the supporting records and a checksum manifest.
Try: “Compare soybean-oil findings across these restaurants, show possible and unassessed items separately, and export the supporting records.” Ingredient research requires a release containing accepted findings; see the research endpoints below.
1. Create an API key
Sign in to your account with an active API subscription. Give your key a name and save it when it appears. The full key is shown only once.
Keep it on your server, in an environment variable or secret manager. Send it in the Authorization header. Never place it in a browser app, a public repository or a URL. You can revoke keys from your account.
2. Find your latest release
A snapshot is a fixed version of the data. This request returns its ID, source dates and restaurant coverage for your plan.
curl --fail-with-body \
-H "Authorization: Bearer $PRUF_API_KEY" \
"https://www.prufapp.com/api/data/v1/snapshots/latest"Keep the returned snapshot_id and release.period_start for subsequent requests. Pass the latter as release_period to retrieve a retained release after a newer one is published. Intelligence includes weekly releases. Earlier subscriptions retain the release schedule in their existing agreement. Source dates describe when each record was collected; a release date does not mean every restaurant was scraped that day.
3. Read menu items
Save this example as pruf-example.mjs and run it with Node.js. It reads every page for the first restaurant in your release. Choose another restaurant using its ID from coverage.
// Node.js 18+. Set PRUF_API_KEY in your server environment.
const base = "https://www.prufapp.com/api/data/v1";
const key = process.env.PRUF_API_KEY;
if (!key) throw new Error("Set PRUF_API_KEY first");
async function get(path) {
const response = await fetch(base + path, {
headers: { Authorization: "Bearer " + key }
});
if (!response.ok) {
throw new Error(response.status + ": " + await response.text());
}
return response.json();
}
const release = await get("/snapshots/latest");
const restaurant = release.coverage[0];
if (!restaurant) throw new Error("No restaurants in this release");
let cursor;
do {
const query = new URLSearchParams({
snapshot_id: release.snapshot_id,
restaurant_id: restaurant.restaurant_id,
limit: "100"
});
if (cursor) query.set("cursor", cursor);
const page = await get("/items?" + query);
console.log(JSON.stringify(page.data, null, 2));
cursor = page.next_cursor;
} while (cursor);Pages contain up to 100 items. Pass next_cursor unchanged to fetch the next page; a null cursor means you have reached the end. Cursors belong to one restaurant and snapshot.
4. Download JSON or CSV
Set SNAPSHOT_ID to the ID from step 2. Downloads include all restaurants in that release. Change format=csv to format=json for JSON.
curl --fail-with-body \
-H "Authorization: Bearer $PRUF_API_KEY" \
"https://www.prufapp.com/api/data/v1/snapshots/$SNAPSHOT_ID/download?format=csv" \
-o pruf-data.csvCSV cells containing lists or objects use JSON text. The release metadata describes the CSV encoding, including spreadsheet-safe escaping.
5. Reproduce an ingredient comparison
Releases that include accepted ingredient findings support canonical ingredient queries. Older releases without findings return analytics_unavailable. Intelligence includes comparisons, complete supporting records and research exports; its availability is shown on the offer page.
| GET endpoint | Use |
|---|---|
| /ingredients | Canonical IDs, names, kinds and comparison methodology version. |
| /search | Search query, restaurant or comma-separated restaurant_ids, disclosure, max_calories, max_sodium and sort; 25 records per page. Nutrient caps use exact numeric amounts. |
| /evidence | Exact item record and accepted findings, selected by item_id. |
| /insights | Basic counts by restaurant, kind and certainty. Possible findings apply to oils. |
| /research/compare | Intelligence: ingredient_id and optional comma-separated restaurant_ids. |
| /research/members | Intelligence: the same comparison plus optional restaurant_id, state and page. Follow next_page until null. |
| /research/export | Intelligence: the full comparison as format=json or format=csv. One request and one download. |
| /research/matrix | Intelligence: up to 12 comma-separated ingredient_ids and optional restaurant_ids; the same counts as the research workspace. |
| /research/matrix-export | Intelligence: the full matrix membership as format=json or format=csv. One request and one download. |
All these endpoints require snapshot_id; include release_period when retaining a dated analysis. Using the get helper above:
const pinned = new URLSearchParams({
snapshot_id: release.snapshot_id,
release_period: release.release.period_start
});
const ingredients = await get('/ingredients?' + pinned);
// Select the ingredient by its canonical ID from ingredients.data.
pinned.set('ingredient_id', selectedIngredientId);
pinned.set('methodology_version', ingredients.methodology_version);
const comparison = await get('/research/compare?' + pinned);
console.log(comparison.manifest, comparison.summary);Counts use distinct menu items. The denominator contains assessed items; confirmed, possible, not matched and unassessed remain distinct. Not matched does not prove absence. A source statement may cover only part of a recipe; this release does not supply a component-specific match.
Save the query, snapshot, period and methodology version together. A saved version that is no longer supported is refused instead of being silently recalculated. Exports always contain the full selected comparison, including unassessed items. JSON contains the manifest and all records. The X-PRUF-Export-Manifest header is base64url-encoded JSON with the file SHA-256 checksum; save it alongside a CSV. Every research CSV cell uses JSON encoding to preserve exact values and prevent spreadsheet formulas.
Multi-ingredient matrices use pruf.ingredient_matrix.v1, returned in the comparison manifest. Keep that methodology version with the selected ingredient IDs and dated release when repeating an analysis.
Subscribers can use the same workflow in the Research Workspace, including browser-local saved analyses and downloadable files. Data Library also provides full JSON/CSV downloads for retained snapshots without creating an API key. Browsing is included separately; data and research exports share your API request and download allowance.
Read the data as disclosed
- A null nutrition value means it was not disclosed, not that it is zero.
none_declaredandsource_not_disclosedare different allergen states. Neither is a guarantee against cross-contact.- Ingredient text preserves alternatives such as “canola and/or soybean oil.” A text match does not prove every alternative is present.
Limits and errors
Each account has 30 requests per minute, 10,000 requests per calendar month and 30 downloads per calendar month. A download also counts as one request. Monthly limits reset at midnight UTC on the first day of the month. Keys on the same account share these limits.
| Response | What to do |
|---|---|
| 400 | Check the query fields, IDs, page size and cursor. |
| 401 | Check your API key. Missing, invalid or revoked keys cannot access data. |
| 403 | Check your subscription and account access. |
| 404 | Check the endpoint, snapshot and retained release_period for your plan. Keep saved analyses pinned; start a new analysis explicitly to use the latest release. |
| 429 | Respect Retry-After for the minute limit, or reset_at for the monthly limit. |
| 503 | The service is temporarily unavailable. Try again later; contact support if it continues. |
Need help? Contact support with the endpoint and error code. Never include your API key.