What it is

A read-only API that lets a restaurant's own developers pull that restaurant's EatOutLog analytics into their own tools. It returns totals and averages only, never information that identifies an individual diner, and the same minimum group sizes as the owner dashboard apply. A restaurant needs the Insights plan and Data API access. Only the restaurant's owner can create credentials, on the Data API page of their owner settings.

Authentication

The API uses OAuth 2.0 client credentials. The owner creates a client and gets a client ID and a client secret (shown once). Swap them for an access token that lasts one hour, then send the token as a Bearer header. Ask for a new token when it expires.

curl -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  https://api.capypine.com/v1/oauth/token

{"access_token":"eol_at_...","token_type":"Bearer","expires_in":3600,"scope":"menu metrics.daily restaurants"}

curl -H "Authorization: Bearer $ACCESS_TOKEN" https://api.capypine.com/v1/restaurants

Secrets start with eol_cs_ and tokens with eol_at_. Never put either in a browser, an app or a public repository.

Scopes

Endpoints

Every response has as_of, the time the figures were computed. Restaurant responses also have time_zone; days are that restaurant's local days. A section the restaurant's plan doesn't include comes back as {"locked": true}. Detail sections accept days (28 to 365, default 30). Any average or rate for a group of fewer than 3 visits, ratings or diners comes back as null.

GET /v1/restaurants

Restaurants this client covers, and any it would cover except that their plan has lapsed (status not_covered). Scope: restaurants.

{"as_of":"2026-10-12T04:00:00Z","restaurants":[{"id":"…","name":"Maple Street Pizzeria","status":"covered","time_zone":"America/Chicago","unlocked_features":["menu_engineering","…"]}]}

GET /v1/metrics/daily?from=2026-09-01&to=2026-09-30

One row per covered restaurant per day, all restaurants in one call. Optional restaurant_id (repeatable). At most 400 days per request. Only finished days are served, so the latest row is yesterday. The taste average needs at least 3 visits with a rated dish and the would-return rate at least 3 answers; otherwise they are null. View, share and impression counts are null unless the restaurant has Impressions over time. Scope: metrics.daily.

{"as_of":"…","from":"2026-09-01","to":"2026-09-30","rows":[{"restaurant_id":"…","date":"2026-09-01","visits":14,"avg_taste":4.3,"pct_would_return":85.7,"restaurant_views":120,"dish_detail_views":64,"dish_photo_views":31,"restaurant_shares":2,"dish_shares":1,"dish_photo_shares":0,"impressions":940}]}

GET /v1/restaurants/{id}/menu

Per-dish scorecard and comment counts per dish (counts only, never comment text). Scope: menu.

{"as_of":"…","restaurant_id":"…","time_zone":"…","data":{"scorecard":{"rows":[…]},"comment_counts":[{"dish_key":"margherita","comments":3}]}}

GET /v1/restaurants/{id}/menu/dishes/{name}

One dish by name. Scope: menu.

{"as_of":"…","restaurant_id":"…","data":{"name":"Margherita","ratings":42,"taste":4.4,…}}

GET /v1/restaurants/{id}/insights

Menu engineering, promotion results and quality alerts. Scope: insights.

{"as_of":"…","restaurant_id":"…","data":{"menu_engineering":{…},"roi":{…},"alerts":[…]}}

GET /v1/restaurants/{id}/benchmarks

Tag, category and price comparisons with nearby restaurants. Scope: benchmarks.

{"as_of":"…","restaurant_id":"…","data":{"tag_comparisons":[…],"category_benchmark":{…},"price_check":{…}}}

GET /v1/restaurants/{id}/guests

First and return visits, repeat visitors, origin areas and post reach. Scope: guests.

{"as_of":"…","restaurant_id":"…","data":{"guests":{…},"repeat_visitors":{…},"origins":[…],"reach":{…}}}

GET /v1/restaurants/{id}/questions

Ask your guests questions with answer counts, hidden below 3 answers. Scope: questions.

{"as_of":"…","restaurant_id":"…","data":{"questions":[{"question":"…","options":["Yes","No"],"answers":12,"counts":[9,3]}]}}

GET /v1/locations/comparison

The owner's covered locations side by side. Scope: locations.

{"as_of":"…","data":{"days":30,"rows":[…]}}

Errors

StatuserrorMeaning
503api_disabledThe Data API isn't open yet.
400unsupported_grant_typeOnly client_credentials is supported.
401invalid_clientUnknown client ID or wrong secret.
401invalid_tokenMissing, expired or revoked access token.
403insufficient_scopeThe client doesn't have that scope.
403restaurant_not_coveredThe restaurant's plan doesn't include the Data API right now.
404not_foundNo such restaurant on this account, or no dish by that name.
429rate_limitedToo many requests. Wait for the number of seconds in Retry-After.

Limits

Per client: 30 requests a minute plus 5 for each covered restaurant, and 500 requests a day for each covered restaurant (days reset at midnight UTC). A nightly pull per restaurant uses a tiny fraction of that. Detail sections are cached for up to 6 hours and daily figures are rebuilt overnight, so polling more often than that returns the same data. A client that keeps calling far past its limit is paused automatically, and the owner is emailed.

Versioning

Fields in /v1 are only ever added, never removed or changed in meaning. A breaking change means /v2, announced in advance, with /v1 kept running for a while alongside it.

Showing the data publicly

Internal use needs nothing. Anything shown to the public must credit EatOutLog and follow section 6 of the Restaurant Operator Terms. The attribution kit has the logo files and wording to use. Use of the API is covered by section 7.

Testing

Ask us for test credentials on our staging environment (https://api.stage.capypine.com), which serve a demo restaurant with sample data, so you can build before going live. Contact sales@capypine.com.