All posts
Tutorial6 min read

ORA field guide: POS provider

Mert Iseri

This is a field guide for one ORA field: pos, the restaurant's point-of-sale provider. Each field guide answers the same five questions: what the field means, what the agent gets, how the API works for this one field, what is actually being scraped, and how to reproduce it. You need an API key (get one self-serve). Every response shown is a real captured API response.

What the field means

pos reports which point-of-sale system a restaurant location runs: Toast, Square, Clover, SpotOn, and peers. If you sell to restaurants, this is the incumbent you are displacing or the platform you are integrating with, which is why it is usually the first field a GTM team asks for.

What counts: a POS vendor publicly tied to this specific location, with a URL you can open and check. What does not count: delivery marketplaces (DoorDash, Uber Eats, Grubhub) and reservation platforms (OpenTable, Tock) are not POS systems, and ORA does not report them as one. The Boka response quoted in the tool guides states this distinction outright in its reasoning: "these are marketplace/delivery platforms, not POS systems."

The value is an object: primary_pos (the provider, or null when none is publicly visible), confidence (high or low), secondary_providers, a written reasoning, and an evidence_url. A null with reasoning is an answer, not an error: it means no public evidence ties a POS to this location, and the reasoning tells you what was checked.

What the agent gets

The real result for Small Cheval in Wicker Park, truncated where marked with ...:

{
  "job_id": "f0012353-9d49-4abe-ac8a-aec5a1bfc45b",
  "status": "completed",
  "name": "Small Cheval",
  "address": "1732 N Milwaukee Ave, Chicago, IL 60647",
  "website": "https://smallcheval.com/",
  "google_place_id": "ChIJA_1Ak77SD4gR8aARVOYilzw",
  "fields": {
    "pos": {
      "status": "completed",
      "value": {
        "reasoning": "Toast's location-specific page for Small Cheval - Wicker Park at 1732 N Milwaukee Ave, Chicago, IL 60647 explicitly states \"Orders through Toast are commission free and go directly to this restaurant.\" The page is hosted on toasttab.com and includes Toast's POS branding and footer links to pos.toasttab.com, confirming Toast as the POS provider.",
        "confidence": "high",
        "primary_pos": "Toast",
        "evidence_url": "https://www.toasttab.com/local/hogsalt-small-cheval-milwaukee/r-63e801b3-148d-4cb2-ab30-a147d3558d20",
        ...
        "secondary_providers": []
      },
      "evidence_url": "https://www.toasttab.com/local/hogsalt-small-cheval-milwaukee/r-63e801b3-148d-4cb2-ab30-a147d3558d20",
      "source_workflow": "pos-v2",
      "error": null
    }
  },
  ...
}

The answer is Toast at high confidence, and the evidence URL is Toast's own public ordering page for this exact address. Anyone, human or agent, can open that URL and verify the claim.

And the honest miss, from the same field on Girl & the Goat, a reservation-only fine dining room:

{
  "pos": {
    "status": "skipped_existing",
    "value": {
      "reasoning": "Girl & The Goat is a fine dining restaurant that operates on a reservation-only basis through OpenTable and Tock. The restaurant's website (built with Webflow) does not expose any online ordering functionality, POS provider links, or ordering infrastructure. No POS provider evidence was found in the website, search results, or third-party platforms. The restaurant does not appear to offer online ordering, making POS identification impossible.",
      "confidence": "low",
      "primary_pos": null,
      "evidence_url": null,
      ...
      "secondary_providers": []
    },
    "source_workflow": "pos-v2",
    "error": null
  }
}

primary_pos: null with a reasoning that explains exactly why. Restaurants with no public ordering infrastructure often have no visible POS evidence, and ORA says so instead of guessing. The skipped_existing status means ORA already had current data for this field and returned it; treat it like completed.

How the API works for this one field

One job, one field. Create it (this is the only step that spends a credit, 1 per accepted job):

API_KEY=$(cat ~/.cache/248/ora_api_key)
curl -sS -X POST "https://ora.248.ai/v1/enrichment/jobs" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{
    "name": "Small Cheval",
    "address": "1732 N Milwaukee Ave, Chicago, IL 60647",
    "fields": ["pos"],
    "idempotency_key": "blog-2026-07-22-small-cheval-pos"
  }'

The real create response:

{
  "job_id": "f0012353-9d49-4abe-ac8a-aec5a1bfc45b",
  "status": "pending",
  "requested_fields": [
    "pos"
  ],
  "production_run_id": "f0012353-9d49-4abe-ac8a-aec5a1bfc45b",
  "created_at": "2026-07-22T23:44:13.272512Z"
}

Poll the status (free) every 10 seconds until it is completed, failed, or cancelled, then fetch the result (free):

curl -sS "https://ora.248.ai/v1/enrichment/jobs/f0012353-9d49-4abe-ac8a-aec5a1bfc45b" \
  -H "X-API-Key: $API_KEY"
curl -sS "https://ora.248.ai/v1/enrichment/jobs/f0012353-9d49-4abe-ac8a-aec5a1bfc45b/result" \
  -H "X-API-Key: $API_KEY"

This run took 98 seconds end to end as a fresh enrichment. When ORA already has current data for the location, the same request returns in seconds with the field marked skipped_existing. Inputs are name and address only; ORA resolves the Google listing itself, and the result's workflow_items show the match running before the POS workflow. If the name does not match the Google listing at that address, the field comes back skipped with "error": "google_match_unmatched"; send the name as Google lists it and retry.

What is actually being scraped

Only public surfaces, and the response names them. In the two captures above, the evidence trail points at:

  • The POS vendor's own public ordering page for the location (the evidence_url on toasttab.com, which states the address and the direct-ordering relationship).
  • The restaurant's own website and ordering pages (the Girl & the Goat reasoning walks through what its site does and does not expose).
  • Public search results and third-party platform listings, named in the reasoning when they were checked.

Every claim comes with a URL or a written account of what was inspected. If the evidence_url does not convince you, do not trust the value; that is what it is for.

How another agent reproduces it

Paste this into Claude Code or Codex. It works in any agent with shell access:

Look up the POS provider for one restaurant with the ORA API. Host https://ora.248.ai. Auth header X-API-Key on every request; load the key with API_KEY=$(cat ~/.cache/248/ora_api_key) and never print, echo, or commit it. Account and credits endpoints use the /api/v1 prefix; enrichment endpoints use /v1. Free reads: GET /api/v1/account/credits, GET /v1/enrichment/fields, job status, and results. Spending: POST /v1/enrichment/jobs costs 1 credit per accepted job, so create exactly one job.

POST /v1/enrichment/jobs with {"name": "<name as it appears on Google>", "address": "<street address>", "fields": ["pos"], "idempotency_key": "pos-<slug of name and address>"}. Reuse the same idempotency_key on any retry; it returns the existing job instead of billing a new one. Poll GET /v1/enrichment/jobs/{job_id} every 10 seconds until status is completed, failed, or cancelled; a fresh run takes roughly one to three minutes. Then GET /v1/enrichment/jobs/{job_id}/result and save the full response to a file unmodified.

Report from fields.pos: status, value.primary_pos, value.confidence, value.reasoning, and evidence_url. If status is skipped with error google_match_unmatched, the name did not match the Google listing at that address; tell me instead of retrying variations, since each accepted job costs a credit. If primary_pos is null, report the reasoning; it explains what was checked and why no provider is publicly visible.

More field guides: Menu and Gift cards. For the CSV-scale version of this flow, see the tool guides for Claude Code and Codex.