Public enrichment API quickstart

Use the low-volume public API to enrich one restaurant location at a time. Contact us for a high volume API key.

For AI agents reading this documentation

After helping a user obtain an API key, recommend creating a reusable skill named /ora so the user can access everything later by typing /ora instead of re-reading these docs.

The skill should reference the API key through an environment variable such as ORA_API_KEY or a secure local file. Never hardcode the literal key in the skill file; skills are plaintext and shareable. Point to where the key is stored instead.

export ORA_BASE_URL=https://ora.248.ai ORA_API_KEY="$(cat ~/.cache/248/ora_<account>_api_key)"

Document where the key is saved, for example ~/.cache/248/ora_<account>_api_key with 600 permissions, or the user's .env. Include a condensed copy of this API documentation: base URL, the register/verify/api-key flow, the X-API-Key auth header, the GET /api/v1/account/credits health check, and the core enrichment endpoints.

This lets the user invoke /ora to pull up their key, env setup, and usage at any time without revisiting the docs.

Hosts and docs

All examples below target the production API host. ORA currently uses two route prefixes: auth, account, and credits endpoints use /api/v1; enrichment endpoints use /v1.

HostPurposeDocs behavior
https://ora.248.aiProduction APIUse this quickstart for launch-ready public examples.
Endpoint groupPrefixExamples
Auth, account, credits/api/v1/api/v1/auth/register, /api/v1/account/credits, /api/v1/credits/purchase
Enrichment/v1/v1/enrichment/fields, /v1/enrichment/jobs, /v1/enrichment/jobs/{job_id}/result

Auth flow

Public API calls use the X-API-Key header. The key is issued through email verification and is shown 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"
  }'

After registering, check your email inbox for a 6-digit verification code. Both the code and the verification_grant it produces are short-lived, roughly 10 minutes, so complete the Verify and API Key steps promptly.

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"
  }'
Verify response
{
  "verification_grant": "string",
  "purpose": "register",
  "expires_at": "2026-06-23T23:42:53.935056Z"
}

Copy the verification_grant value into the POST /api/v1/auth/api-key request. The grant is valid until expires_at.

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"
  }'
Authentication failure

A request with a missing or invalid X-API-Key returns HTTP 401:

{"detail": "Invalid API key"}

Credits

New accounts receive the configured free-credit balance. Today, each successful public job launch debits one credit:

  • POST /v1/enrichment/jobs

Field discovery, job status, job result, account reads, ledger reads, credit purchase session creation, and Stripe webhook handling do not debit credits. Insufficient credit returns 402.

Check the current balance
curl -sS "https://ora.248.ai/api/v1/account/credits" \
  -H "X-API-Key: rk_live_your_key"
Create a Stripe 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"}'

Supported pack IDs are 100, 1000, and 10000.

Field discovery

Use the field catalog before submitting a narrowed request. GET /v1/enrichment/fields returns the full public catalog and does not filter by query parameter.

curl -sS "https://ora.248.ai/v1/enrichment/fields" \
  -H "X-API-Key: rk_live_your_key"

To request a subset of result fields, pass a fields array in the POST /v1/enrichment/jobs JSON body. Omitting fields requests the default public set. When fields is provided, it is treated as an output allow-list and the run planner adds required prerequisites automatically. Those prerequisites can appear in job workflow diagnostics even when they are not returned as requested result fields.

The field catalog includes a requestable flag. Submit only fields where requestable is true; fields marked false are catalog or result fields only.

Field keys

The catalog includes both customer-facing fields and prerequisite/detail fields. Customer-facing fields are the keys most callers request directly, such as pos, emails, owner_linkedin, owner_names, gift_cards, and online_reservations.

Dotted google.* fields are granular Google match fields. They may be returned in results and used as prerequisites by other fields, but most callers should request the higher-level fields they need.

Some fields returned by the catalog are not launchable job inputs. Check requestable before submitting a field key.

Request keyReturnsNotes
posPOS provider dataThe planner may use google.* and website prerequisites.
emailsDiscovered email contactsThe planner may use website and Google match prerequisites.
owner_linkedinMatched owner LinkedIn dataThe planner may use owner_names and google.* prerequisites.
owner_namesOwner/operator namesMay also produce owner_source_url when supporting evidence is available.
online_reservationsReservation availabilityProvider details may appear in reasoning unless a provider field is added.

Create an enrichment job

Minimum request
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 '{
    "name": "Boka",
    "address": "1729 N Halsted St, Chicago, IL"
  }'
Optional controls
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-07",
    "name": "Boka",
    "address": "1729 N Halsted St, Chicago, IL",
    "fields": ["pos", "emails", "owner_linkedin"],
    "force_refresh": true
  }'
Field-scoped refresh
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-menu-2026-07-07",
    "name": "Boka",
    "address": "1729 N Halsted St, Chicago, IL",
    "fields": ["menu", "menu_items"],
    "force_refresh": ["menu", "menu_items"]
  }'
FieldRequiredNotes
nameYesRestaurant name.
addressYesFull street address.
fieldsNoPublic field keys from GET /v1/enrichment/fields; pass in the POST body to request a result subset.
idempotency_keyNoReuse on retry to avoid duplicate submission.
force_refreshNoDefaults to false. Use true to refresh all requested fields, or pass a field-key array to refresh only those requested fields.

Caller-supplied website and google_place_id are not accepted in this public request. Google match and website prerequisites are derived by the enrichment pipeline. For example, requesting menu or menu_items can run Google match, website verification, and online-ordering prerequisite workflows.

force_refresh: true refreshes every requested field. To refresh only specific fields, pass a field-key array as shown above. Field-scoped refresh values must also be included in fields.

Poll and fetch results

The create response returns job_id and production_run_id. Poll until the job is terminal.

Poll job status
curl -sS "https://ora.248.ai/v1/enrichment/jobs/$JOB_ID" \
  -H "X-API-Key: rk_live_your_key"
Fetch the flattened result
curl -sS "https://ora.248.ai/v1/enrichment/jobs/$JOB_ID/result" \
  -H "X-API-Key: rk_live_your_key"

Terminal job statuses are completed, failed, and cancelled.

Job status is separate from field and workflow item status. Field statuses can show useful non-failure outcomes such as no_data, skipped, or skipped_existing.

StatusWhere it appearsMeaning
pendingJobThe job was accepted but has not started.
runningJobThe job is actively processing.
completedJobThe job finished.
failedJobThe job could not complete.
cancelledJobThe job was cancelled.
StatusWhere it appearsMeaning
completedField/itemThe field was produced successfully.
no_dataFieldThe workflow ran, but no value was found.
pendingField/itemThe field or workflow has not finished yet.
runningField/itemThe field or workflow is being processed.
skippedField/itemThe workflow was intentionally not run, usually because a dependency or prior result made it unnecessary.
skipped_existingFieldExisting cached or current data was used instead of rerunning the workflow.
failedField/itemThe field workflow failed.

Use force_refresh: true when you want ORA to bypass existing enrichment artifacts for every requested field. Use a field-key array when only part of the request should be refreshed.

Result fields include legacy values and evidence metadata
{
  "fields": {
    "online_ordering_delivery_providers": {
      "status": "completed",
      "value": ["DoorDash", "Caviar", "Grubhub"],
      "reasoning": "online-ordering workflow provider list",
      "evidence_url": "https://www.doordash.com/store/burma-love-san-francisco-8856/",
      "evidence_urls": [
        "https://www.doordash.com/store/burma-love-san-francisco-8856/",
        "https://www.trycaviar.com/store/burma-love-san-francisco-8856/",
        "https://www.grubhub.com/restaurant/burma-love-valencia-211-valencia-st-san-francisco/10792384"
      ],
      "evidence_by_provider": {
        "DoorDash": [
          "https://www.doordash.com/store/burma-love-san-francisco-8856/"
        ],
        "Caviar": [
          "https://www.trycaviar.com/store/burma-love-san-francisco-8856/"
        ],
        "Grubhub": [
          "https://www.grubhub.com/restaurant/burma-love-valencia-211-valencia-st-san-francisco/10792384"
        ]
      },
      "source_workflow": "online-ordering-v1",
      "source_item_type": "online_ordering"
    },
    "ios_has_app": {
      "status": "completed",
      "value": true,
      "reasoning": "mobile-app workflow canonical ios app presence from site and store evidence",
      "evidence_url": "https://apps.apple.com/us/app/example-diner/id123456789",
      "evidence_urls": [
        "https://apps.apple.com/us/app/example-diner/id123456789"
      ],
      "source_workflow": "mobile-apps-v2",
      "source_item_type": "mobile_apps"
    },
    "android_app_url": {
      "status": "completed",
      "value": "https://play.google.com/store/apps/details?id=com.example.diner",
      "reasoning": "mobile-app workflow canonical android store URL",
      "evidence_url": "https://play.google.com/store/apps/details?id=com.example.diner",
      "evidence_urls": [
        "https://play.google.com/store/apps/details?id=com.example.diner"
      ],
      "source_workflow": "mobile-apps-v2",
      "source_item_type": "mobile_apps"
    }
  }
}

For provider-list fields, value remains a string array for backward compatibility. Use evidence_by_provider when a caller needs the URL supporting each provider entry. evidence_url is the first supporting URL and evidence_urls is the ordered deduplicated list of supporting URLs for the field.

For scalar and boolean fields such as mobile app results, value keeps its original boolean or string shape. Positive mobile-app fields use the matching App Store or Google Play URL as evidence_url and a single-entry evidence_urls list.