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

For AI agents reading this documentation

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.

ProductRoutesAuthenticationAccess
Signup and API keys/api/v1/auth/*No key for signup; X-API-Key for rotationSelf-service
Credits/api/v1/account/*, /api/v1/credits/*X-API-KeySelf-service
Enrichment/v1/enrichment/*X-API-KeySelf-service; uses standard credits
Advanced Search*/v1/locations/searchAuthorization: Bearer <issued key>Contact us for access
New Restaurant Openings*/v1/locations/searchAuthorization: Bearer <issued key>Contact us for access
Online Ordering Uptime*/v1/enrichment/online-ordering/open-status-hours/*X-API-KeyContact 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.

1. Start registration
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"
  }'
2. Verify the emailed code
curl -sS -X POST "https://ora.248.ai/api/v1/auth/verify" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "operator@example.com",
    "code": "123456"
  }'
3. Exchange the grant for an API key
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"
  }'
Store the returned key securely. Authenticated self-service calls use X-API-Key. Verification codes and grants expire after approximately ten minutes.

Key recovery and rotation

TaskStart routeCompletion
Recover a lost keyPOST /api/v1/auth/forgot-api-keyVerify the emailed code, then exchange the grant at POST /api/v1/auth/api-key/recover.
Rotate an active keyPOST /api/v1/auth/api-key/rotate/requestAuthenticate 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.

MethodRoutePurpose
GET/api/v1/account/creditsReturn the current standard-credit balance.
GET/api/v1/account/ledger?limit=20Return newest-first credit activity. Limit: 1-100.
POST/api/v1/credits/purchaseCreate a hosted Stripe Checkout session.
Check balance
curl -sS "https://ora.248.ai/api/v1/account/credits" \
  -H "X-API-Key: rk_live_your_key"
Read ledger
curl -sS "https://ora.248.ai/api/v1/account/ledger?limit=20" \
  -H "X-API-Key: rk_live_your_key"
Create a Checkout session
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_idCreditsPrice
100100$100
10001,000$1,000
1000010,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.

MethodRoutePurpose
GET/v1/enrichment/fieldsList field keys and whether each field is requestable.
POST/v1/enrichment/jobsCreate one restaurant enrichment job.
GET/v1/enrichment/jobs/{job_id}Poll job and workflow-item status.
GET/v1/enrichment/jobs/{job_id}/resultFetch the flattened field result.
Discover fields
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.

Create a job
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
  }'
FieldRequiredNotes
nameYesRestaurant name.
addressYesFull street address.
fieldsNoRequestable field keys. Omit for the default public set.
idempotency_keyNoReuse the same key and body after a transport failure.
force_refreshNofalse, true, or a subset of the requested field keys.
Reuse the same 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.
Poll status
curl -sS "https://ora.248.ai/v1/enrichment/jobs/$JOB_ID" \
  -H "X-API-Key: rk_live_your_key"
Fetch result
curl -sS "https://ora.248.ai/v1/enrichment/jobs/$JOB_ID/result" \
  -H "X-API-Key: rk_live_your_key"
StatusMeaning
pendingAccepted but not started.
runningActively processing.
pausedTemporarily paused and still non-terminal.
completedFinished; fetch the result.
failedCould not complete.
cancelledCancelled 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 requires separate access

Create your Ora account first, then contact us for access.

Search locations
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}
  }'
Count locations
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}
  }'
PropertyValuesNotes
modesearch or countcount returns only the matching total.
textOptionalFull-text query over up to five allowlisted text fields.
filtersOptionalUp to 25 positive structured filters.
must_notOptionalUp to 25 exclusion filters.
sortOptionalUp to eight allowlisted deterministic sorts.
includeOptionalReturn selected field values and complete field-result audit payloads.
pageOptionalsize is 1-100; offset is 0-100.
Structured filter operators are 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

FamilyFields
Locationlocation_id, location.name, location.city, location.state, location.zip, location.identity_source, location.created_at, location.updated_at
Googlegoogle.business_name, google.city, google.state, google.zip, google.business_status, google.rating, google.rating_count
Restaurant attributesmerchant.type, merchant.group, opening_date, chain.status, chain.is_chain, pos.provider
Presencepresence.website, presence.owner_names, presence.owner_linkedin, presence.facebook_page, presence.instagram_page, presence.emails, presence.menu
Featuresfeatures.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 fieldsonline_ordering.delivery_providers, online_ordering.pickup_providers, gift_cards.provider
Text fieldsmenu, negative_reviews, owner_names, parent_company, business_name
Field freshnessfield.<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.

Online Ordering Uptime requires separate access

Create your Ora account first, then contact us for access.

Uptime access is a separate account entitlement with a weekly quota. A job reserves one weekly uptime credit. Conclusive true or false outcomes are charged; inconclusive and failed checks release the reservation. This quota is separate from standard enrichment credits.
Start with name and address
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"
  }'
Or start with a known location ID
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.

Poll the job
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.

Uptime responses expose request, rate-limit, and weekly usage headers, including 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 statusMeaning
400The request or verification grant is invalid.
401The API key is missing or invalid.
402The standard-credit balance is insufficient for enrichment.
403The account is not enabled for the requested restricted product.
404The account, job, location, or result was not found.
409An idempotency key was reused with a different request.
422The request body failed validation.
429A rate or weekly usage limit was exceeded; honor Retry-After.
502 / 503A provider or required service is temporarily unavailable.
Rate limits

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.