Everything you need to enrich your first restaurant.
Sign up, buy credits, enrich individual restaurant locations, run advanced searches, find new restaurant openings, and check online ordering uptime.
Production base URL: https://ora.248.ai
After helping a user obtain an API key, create a reusable skill named /ora. Keep it lightweight: reference the key securely through ORA_API_KEY and use 248.ai/docs as the canonical API reference. Do not copy the API documentation into the skill.
| Product | Routes | Authentication | Access |
|---|---|---|---|
| Signup and API keys | /api/v1/auth/* | No key for signup; X-API-Key for rotation | Self-service |
| Credits | /api/v1/account/*, /api/v1/credits/* | X-API-Key | Self-service |
| Enrichment | /v1/enrichment/* | X-API-Key | Self-service; uses standard credits |
| Advanced Search* | /v1/locations/search | Authorization: Bearer <issued key> | Contact us for access |
| New Restaurant Openings* | /v1/locations/search | Authorization: Bearer <issued key> | Contact us for access |
| Online Ordering Uptime* | /v1/enrichment/online-ordering/open-status-hours/* | X-API-Key | Contact us to enable the account |
* Advanced Search, New Restaurant Openings, and Online Ordering Uptime require separate commercial access. Contact us to enable them.
Signup and API keys
Signup is self-service. Ora emails a six-digit code, returns a short-lived verification grant after verification, and shows the issued API key once.
curl -sS -X POST "https://ora.248.ai/api/v1/auth/register" \
-H "Content-Type: application/json" \
-d '{
"email": "operator@example.com",
"first_name": "Ada",
"last_name": "Lovelace"
}'curl -sS -X POST "https://ora.248.ai/api/v1/auth/verify" \
-H "Content-Type: application/json" \
-d '{
"email": "operator@example.com",
"code": "123456"
}'curl -sS -X POST "https://ora.248.ai/api/v1/auth/api-key" \
-H "Content-Type: application/json" \
-d '{
"verification_grant": "verification-grant-from-verify"
}'X-API-Key. Verification codes and grants expire after approximately ten minutes.Key recovery and rotation
| Task | Start route | Completion |
|---|---|---|
| Recover a lost key | POST /api/v1/auth/forgot-api-key | Verify the emailed code, then exchange the grant at POST /api/v1/auth/api-key/recover. |
| Rotate an active key | POST /api/v1/auth/api-key/rotate/request | Authenticate with X-API-Key, verify the emailed code, then exchange the grant at POST /api/v1/auth/api-key/rotate. |
Credits
New self-signup accounts currently receive 50 standard credits. Each accepted enrichment job debits one credit. Field discovery, status polls, result reads, account reads, and Checkout session creation do not debit credits.
Online Ordering Uptime uses a separate weekly entitlement and does not draw from this standard-credit balance.
| Method | Route | Purpose |
|---|---|---|
| GET | /api/v1/account/credits | Return the current standard-credit balance. |
| GET | /api/v1/account/ledger?limit=20 | Return newest-first credit activity. Limit: 1-100. |
| POST | /api/v1/credits/purchase | Create a hosted Stripe Checkout session. |
curl -sS "https://ora.248.ai/api/v1/account/credits" \
-H "X-API-Key: rk_live_your_key"curl -sS "https://ora.248.ai/api/v1/account/ledger?limit=20" \
-H "X-API-Key: rk_live_your_key"curl -sS -X POST "https://ora.248.ai/api/v1/credits/purchase" \
-H "Content-Type: application/json" \
-H "X-API-Key: rk_live_your_key" \
-d '{"pack_id": "100"}'Open the returned checkout_url to complete payment.
| pack_id | Credits | Price |
|---|---|---|
| 100 | 100 | $100 |
| 1000 | 1,000 | $1,000 |
| 10000 | 10,000 | $10,000 |
Restaurant enrichment
Enrich one restaurant location at a time. Discover the available fields, create an asynchronous job, poll until terminal, and fetch the flattened result.
| Method | Route | Purpose |
|---|---|---|
| GET | /v1/enrichment/fields | List field keys and whether each field is requestable. |
| POST | /v1/enrichment/jobs | Create one restaurant enrichment job. |
| GET | /v1/enrichment/jobs/{job_id} | Poll job and workflow-item status. |
| GET | /v1/enrichment/jobs/{job_id}/result | Fetch the flattened field result. |
curl -sS "https://ora.248.ai/v1/enrichment/fields" \
-H "X-API-Key: rk_live_your_key"Submit only fields where requestable is true. The planner automatically adds prerequisites required to produce the requested outputs.
curl -sS -X POST "https://ora.248.ai/v1/enrichment/jobs" \
-H "Content-Type: application/json" \
-H "X-API-Key: rk_live_your_key" \
-d '{
"idempotency_key": "boka-2026-07-22",
"name": "Boka",
"address": "1729 N Halsted St, Chicago, IL",
"fields": ["pos", "emails", "owner_linkedin"],
"force_refresh": false
}'| Field | Required | Notes |
|---|---|---|
| name | Yes | Restaurant name. |
| address | Yes | Full street address. |
| fields | No | Requestable field keys. Omit for the default public set. |
| idempotency_key | No | Reuse the same key and body after a transport failure. |
| force_refresh | No | false, true, or a subset of the requested field keys. |
idempotency_key and identical body after a timeout or transport failure. Ora returns the existing job without another credit charge. Supplying a different body with the same key returns HTTP 409.curl -sS "https://ora.248.ai/v1/enrichment/jobs/$JOB_ID" \
-H "X-API-Key: rk_live_your_key"curl -sS "https://ora.248.ai/v1/enrichment/jobs/$JOB_ID/result" \
-H "X-API-Key: rk_live_your_key"| Status | Meaning |
|---|---|
| pending | Accepted but not started. |
| running | Actively processing. |
| paused | Temporarily paused and still non-terminal. |
| completed | Finished; fetch the result. |
| failed | Could not complete. |
| cancelled | Cancelled before completion. |
Example result
{
"job_id": "5be0a05c-4ef9-4e31-b9b0-69a412fa9dc0",
"status": "completed",
"name": "Boka",
"address": "1729 N Halsted St, Chicago, IL",
"location_id": "2a45f697-391b-4902-9287-7e204b224d96",
"fields": {
"pos": {
"status": "completed",
"value": "Toast",
"reasoning": "Provider evidence found on the restaurant website.",
"evidence_url": "https://example.com/order",
"evidence_urls": ["https://example.com/order"],
"source_workflow": "pos-v1",
"source_item_type": "pos"
}
}
}Every requested field has its own status and value. When available, Ora also returns reasoning, evidence URLs, and source workflow metadata.
Advanced Search*
Search Ora's enriched restaurant database through a controlled JSON API backed by OpenSearch. The endpoint does not accept raw OpenSearch queries.
Create your Ora account first, then contact us for access.
curl -sS -X POST "https://ora.248.ai/v1/locations/search" \
-H "Authorization: Bearer your_issued_location_search_key" \
-H "Content-Type: application/json" \
-d '{
"mode": "search",
"text": {
"query": "tacos al pastor",
"fields": ["menu"]
},
"filters": [
{"field": "location.state", "op": "eq", "value": "CA"},
{"field": "features.supports_delivery", "op": "eq", "value": true}
],
"sort": [
{"field": "google.rating_count", "direction": "desc"}
],
"include": {
"fields": ["pos", "online_ordering_delivery_providers"],
"field_results": true
},
"page": {"size": 25, "offset": 0}
}'curl -sS -X POST "https://ora.248.ai/v1/locations/search" \
-H "Authorization: Bearer your_issued_location_search_key" \
-H "Content-Type: application/json" \
-d '{
"mode": "count",
"filters": [
{"field": "location.state", "op": "eq", "value": "CA"},
{"field": "pos.provider", "op": "eq", "value": "Toast"}
]
}'New restaurant openings
Advanced Search can also find new restaurant openings through the same endpoint. Filter and sort on opening_date using YYYY-MM values. Our new-openings pipeline continuously seeds newly discovered restaurants into the same search index.
curl -sS -X POST "https://ora.248.ai/v1/locations/search" \
-H "Authorization: Bearer your_issued_location_search_key" \
-H "Content-Type: application/json" \
-d '{
"mode": "search",
"filters": [
{"field": "opening_date", "op": "gte", "value": "2026-07"},
{"field": "opening_date", "op": "lte", "value": "2026-09"}
],
"sort": [
{"field": "opening_date", "direction": "desc"}
],
"page": {"size": 100, "offset": 0}
}'| Property | Values | Notes |
|---|---|---|
| mode | search or count | count returns only the matching total. |
| text | Optional | Full-text query over up to five allowlisted text fields. |
| filters | Optional | Up to 25 positive structured filters. |
| must_not | Optional | Up to 25 exclusion filters. |
| sort | Optional | Up to eight allowlisted deterministic sorts. |
| include | Optional | Return selected field values and complete field-result audit payloads. |
| page | Optional | size is 1-100; offset is 0-100. |
eq, neq, in, exists, gt, gte, lt, lte. Each field supports only the operators appropriate for its type. An in filter accepts at most 100 values.Supported query field families
| Family | Fields |
|---|---|
| Location | location_id, location.name, location.city, location.state, location.zip, location.identity_source, location.created_at, location.updated_at |
| google.business_name, google.city, google.state, google.zip, google.business_status, google.rating, google.rating_count | |
| Restaurant attributes | merchant.type, merchant.group, opening_date, chain.status, chain.is_chain, pos.provider |
| Presence | presence.website, presence.owner_names, presence.owner_linkedin, presence.facebook_page, presence.instagram_page, presence.emails, presence.menu |
| Features | features.supports_online_ordering, features.supports_delivery, features.supports_pickup, features.supports_gift_cards, features.supports_catering, features.accepts_ebt, features.cash_only, features.has_bar |
| Provider fields | online_ordering.delivery_providers, online_ordering.pickup_providers, gift_cards.provider |
| Text fields | menu, negative_reviews, owner_names, parent_company, business_name |
| Field freshness | field.<field_key>.observed_at and field.<field_key>.updated_at |
Example response
{
"mode": "search",
"total": 418,
"results": [
{
"location_id": "2a45f697-391b-4902-9287-7e204b224d96",
"score": 8.42,
"location": {
"name": "Example Taqueria",
"city": "San Francisco",
"state": "CA"
},
"fields": {
"pos": "Toast",
"online_ordering_delivery_providers": ["Uber Eats"]
},
"field_results": {}
}
],
"page": {"size": 25, "offset": 0},
"metadata": {}
}Online Ordering Uptime*
Start an asynchronous check to determine whether a matched restaurant is currently accepting orders on Uber Eats, DoorDash, or Grubhub. Submit either a known Ora location ID or a restaurant name and address, but not both.
Create your Ora account first, then contact us for access.
true or false outcomes are charged; inconclusive and failed checks release the reservation. This quota is separate from standard enrichment credits.curl -sS -X POST \
"https://ora.248.ai/v1/enrichment/online-ordering/open-status-hours/jobs" \
-H "Content-Type: application/json" \
-H "X-API-Key: rk_live_your_key" \
-H "Idempotency-Key: tartine-uptime-2026-07-28" \
-d '{
"name": "Tartine Bakery",
"address": "600 Guerrero St, San Francisco, CA 94110"
}'curl -sS -X POST \
"https://ora.248.ai/v1/enrichment/online-ordering/open-status-hours/jobs" \
-H "Content-Type: application/json" \
-H "X-API-Key: rk_live_your_key" \
-H "Idempotency-Key: location-uptime-2026-07-28" \
-d '{
"location_id": "c6126ebf-9586-422d-9e7a-31095f79e3f4"
}'The optional Idempotency-Key header makes identical transport retries return the original job. Unlike the enrichment endpoint, uptime idempotency is sent in a header.
curl -sS \
"https://ora.248.ai/v1/enrichment/online-ordering/open-status-hours/jobs/$JOB_ID" \
-H "X-API-Key: rk_live_your_key"Poll until status is completed, failed, or cancelled. While a job is queued or running, honor the response's Retry-After header.
Completed response
{
"job_id": "0b50aa5c-4b05-4b42-9c04-cafd9c898876",
"request": {
"name": "Tartine Bakery",
"address": "600 Guerrero St, San Francisco, CA 94110"
},
"location_id": "c6126ebf-9586-422d-9e7a-31095f79e3f4",
"status": "completed",
"created_at": "2026-07-28T21:49:37.968516Z",
"checked_at": "2026-07-28T21:50:04.507231Z",
"finished_at": "2026-07-28T21:50:09.798203Z",
"result": {
"google": {
"place_id": "ChIJBVY2Bxh-j4ARa2zO8Jd6H2A",
"name": "Tartine Bakery",
"address": "600 Guerrero St, San Francisco, CA 94110, USA"
},
"uber_eats": {
"accepting_orders": true,
"reason": "accepting_orders",
"url": "https://www.ubereats.com/store/tartine-bakery/B44a6r9vXumvKlrvPZmrmw?diningMode=PICKUP"
}
}
}Read each provider's accepting_orders result
true: the provider page shows that the store is accepting orders.false: the provider explicitly identifies the store as closed, removed, or not found.null: available page evidence is inconclusive.
Destination-specific delivery or pickup availability does not by itself mark a store closed. The example above shows the common provider result shape.
X-Request-ID, X-RateLimit-*, X-248-Job-Billing-Status, X-248-Weekly-Credits-Remaining.Errors and limits
Error responses include a human-readable message. Uptime errors additionally use a stable machine-readable error code and retryable flag.
| HTTP status | Meaning |
|---|---|
| 400 | The request or verification grant is invalid. |
| 401 | The API key is missing or invalid. |
| 402 | The standard-credit balance is insufficient for enrichment. |
| 403 | The account is not enabled for the requested restricted product. |
| 404 | The account, job, location, or result was not found. |
| 409 | An idempotency key was reused with a different request. |
| 422 | The request body failed validation. |
| 429 | A rate or weekly usage limit was exceeded; honor Retry-After. |
| 502 / 503 | A provider or required service is temporarily unavailable. |
Enrichment and Advanced Search currently allow up to 6,000 requests per hour in their respective buckets. Online Ordering Uptime create and status routes each use a separate 600-request/hour bucket. Limits may vary by plan. On HTTP 429 or a retryable 503, wait for the number of seconds in Retry-After.