PRūF Data API
Your first API request.
Connect ingredient lists, allergen disclosures and nutrition to restaurant menus. Explore one restaurant or download a complete data release.
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://prufapp.com/api/data/v1/snapshots/latest"Keep the returned snapshot_id for subsequent requests. 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://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://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.
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 and coverage. For snapshot_not_found, fetch the latest release and restart pagination. |
| 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.