Machine interface

Stop scraping. Start asking.

Every OrgAInism entity is addressable. Reads are open: CORS *, no auth, no key. Responses carry the stream (declared / observed) behind every field, so your agent can reason about disagreement instead of averaging it away.

The body has two faces. Machines eat at https://q-and-a.ai — text and JSON only, no HTML, no JavaScript, no images, with the homepage serving the llms.txt body itself and a live buffet tree at /{trade}/{st}/{city}.txt. People read the same records at https://orgainism.com. Every machine record carries a claim_url back to the human face.

base https://q-and-a.aiOpenAPI 3.1 spec/llms.txt/sitemap.xml

The serving gate

You will never receive a dead link from this organism.

Records are quarantined from circulation the moment the heartbeat stops. Only status=active entities appear in search results, referrals, related-entity blocks and converse recommendations. Out-of-circulation records stay fully readable at their direct endpoint with a serving block explaining why — transparency, not erasure. Ask for them with ?include=archived.

Died — never blacklisted

Dead site, unreachable phone or a lapsed license quarantines a record instantly, then a grace window of 3 rechecks, then archived with a died_at. History is kept in full and a single fresh observation showing life restores the record automatically.

Lied — blacklisted

A declared license contradicted by the registry as revoked/suspended/fake, a fraudulent claim, or declared data disproven by 2+ observed sources floors trust to 0, revokes the “speaking for itself” badge and serves a neutral inactive federation badge. Re-entry costs registry-phone re-verification plus operator approval, and trust restarts from 0.

Every transition lands in the delta feed as quarantined | archived | restored | blacklisted, so a synced consumer learns about a death without re-crawling. Coverage is separate from location: served_areas[] plus coverage_grants[] show cities inherited when a neighbour went dark, labeled “serves {city} · located {home_city}”. The physical address is never fabricated.

GET/api/public/entity?id={uuid}

Full reconciled record

The complete living record: entity, reconciled_fields with agreement markers, both raw streams, divergences, trust breakdown, vitality, revision history, license state, observation sources, the serving-gate status block, served_areas and coverage grants.

GET /api/public/entity
curl -s "https://q-and-a.ai/api/public/entity?id=ENTITY_UUID" | jq '{
  name: .entity.name,
  vitality,
  trust_score,
  divergences: [.divergences[] | {field, declared_value, observed_value}]
}'
GET/api/public/entities?trade=&state=&vitality=&q=&limit=

Search the organism

Compact list. Each item includes its own endpoint URL so an agent can crawl one hop deeper without guessing routes.

GET /api/public/entities
curl -s "https://q-and-a.ai/api/public/entities?trade=Plumbing&state=AZ&limit=5"

# filters: trade, state (2-letter), vitality=alive|dormant|unverified|divergent,
#          q (name/city substring), limit (1-200, default 25)
#          include=archived -> opt out of the serving gate and receive
#          quarantined / archived / blacklisted records too
#
# Trade synonyms are normalized before matching: q=roofer -> Roofing,
# q="ac repair" -> HVAC, q="flower shop" -> Landscaping. When an alias
# matches, the response carries trade_resolution: { matched_alias, resolved_trade }.
POST/api/public/converse

Talk to the entity

The entity answers in the first person, strictly from its record, citing the stream per fact. If something isn't in the record it says so instead of inventing it.

POST /api/public/converse
curl -s -X POST "https://q-and-a.ai/api/public/converse" \
  -H 'content-type: application/json' \
  -d '{
    "entity_id": "ENTITY_UUID",
    "message": "Are you licensed to work in Arizona right now?"
  }'

# -> { "answer": "...", "citations": [{ "field": "license_status", "stream": "observed" }],
#      "revision": 7, "trust_score": 94, "vitality": "alive" }
GET/api/public/changes?since={seq}&limit=500flagship

Sync once. Sip diffs forever. Never crawl us twice.

A single global, monotonically increasing change sequence covers the whole organism. Every ingest, declaration and reconciliation appends one row with the fields that actually moved. Full-sync once, remember latest_seq, then poll this endpoint forever — you only pay for what changed.

GET /api/public/changes
# 1. full sync once
curl -s "https://q-and-a.ai/api/public/entities?limit=200"

# 2. then sip the deltas — store latest_seq between calls
curl -s "https://q-and-a.ai/api/public/changes?since=0&limit=500"

# -> {
#      "latest_seq": 1841,
#      "has_more": false,
#      "changes": [{
#        "seq": 1837,
#        "entity_id": "…",
#        "entity_endpoint": "https://q-and-a.ai/api/public/entity?id=…",
#        "revision": 12,
#        "stream": "observed",
#        "change_type": "modified",
#        "fields_changed": ["phone", "license_status"],
#        "created_at": "2026-07-26T12:00:00Z"
#      }]
#    }
POST/api/public/ingest

Push observations (crawler workers)

Upserts entities, appends the observed stream, recomputes trust, vitality and divergences, and bumps the revision. Requires a bearer token equal to the INGEST_API_KEY secret; until that secret is set the endpoint fails closed with 503.

POST /api/public/ingest
curl -s -X POST "https://q-and-a.ai/api/public/ingest" \
  -H 'authorization: Bearer $INGEST_API_KEY' \
  -H 'content-type: application/json' \
  -d '{
    "observations": [{
      "name": "Acme Plumbing Co.",
      "trade": "Plumbing",
      "phone": "(602) 555-0142",
      "address": "1420 W Van Buren St, Phoenix, AZ 85007",
      "city": "Phoenix", "state": "AZ",
      "license_number": "ROC-12345",
      "license_status": "active",
      "website": "https://acmeplumbingaz.com",
      "source": "crawler", "source_url": "https://acmeplumbingaz.com/contact",
      "liveness": { "website_status": "up", "phone_valid": "true" },
      "fields": { "hours": "Mon-Sat 7:00-19:00" }
    }]
  }'
POST/api/public/bulk

Bulk store precomputed records (operators only)

The loading dock. Zero reconciliation: the worker has already computed trust_score, vitality and normalized license_status, so this route only stores finished records as fast as multi-row inserts allow (1..1000 entities per request). Entities whose (name, trade) already exists are skipped. Same bearer token as ingest. Use /api/public/ingest for incremental, live pushes that need reconciliation.

POST /api/public/bulk
curl -s -X POST "https://q-and-a.ai/api/public/bulk" \
  -H 'authorization: Bearer $INGEST_API_KEY' \
  -H 'content-type: application/json' \
  -d '{
    "entities": [{
      "name": "Acme Plumbing Co.",
      "trade": "Plumbing",
      "city": "Phoenix", "state": "AZ",
      "phone": "(602) 555-0142",
      "license_number": "ROC-12345", "license_status": "active",
      "trust_score": 78, "vitality": "alive",
      "observations": [
        { "field": "phone", "value": "(602) 555-0142", "source": "registry" },
        { "field": "license_status", "value": "active", "source": "registry" }
      ]
    }]
  }'

# -> {"inserted":1,"skipped":0}
GET/api/public/crawl-tasks

What the machines are asking for (crawler workers)

The misses are the map. Every question an entity could not answer and every search that returned nothing becomes a deduped, count-ranked crawl task. Same bearer token as ingest. POST { resolve: [ids] } to mark tasks done.

GET /api/public/crawl-tasks
curl -s "https://q-and-a.ai/api/public/crawl-tasks" \
  -H 'authorization: Bearer $INGEST_API_KEY'

# -> { "tasks": [
#      { "id": "entity:…", "type": "entity_gap", "entity_id": "…",
#        "query": "do you do emergency weekend calls", "count": 14, "last_seen": "…" },
#      { "id": "coverage:…", "type": "coverage_gap",
#        "trade": "Roofing", "state": "NV", "count": 9, "last_seen": "…" } ] }

curl -s -X POST "https://q-and-a.ai/api/public/crawl-tasks" \
  -H 'authorization: Bearer $INGEST_API_KEY' \
  -H 'content-type: application/json' \
  -d '{"resolve":["entity:…"]}'
GET/api/public/demand-queries?limit=500

The query log — complete demand picture (internal workers)

Every distinct query the organism has received, answered or not: search hits, search misses and converse questions, deduped and ordered by count desc. Misses alone feed crawl-tasks; this is the whole picture, so a worker can materialize static packages for demand that actually exists. Same bearer token as ingest. Trade synonyms are normalized before matching (roofer -> Roofing, ac repair -> HVAC, flower shop -> Landscaping) and the resolved trade is what gets logged.

GET /api/public/demand-queries
curl -s "https://q-and-a.ai/api/public/demand-queries?limit=500" \
  -H 'authorization: Bearer $INGEST_API_KEY'

# -> { "count": 312, "answered": 240, "unanswered": 72,
#      "queries": [
#        { "q": "ac repair", "trade": "HVAC", "state": "AZ", "city": null,
#          "count": 41, "last_seen": "…", "answered": true, "sources": ["search"] },
#        { "q": "roofer", "trade": "Roofing", "state": "NV", "city": null,
#          "count": 18, "last_seen": "…", "answered": false,
#          "sources": ["search", "search_miss"] } ] }

# answered = the query matched at least one in-circulation record.
# unanswered rows are the same demand crawl-tasks is already chasing.

The vacancy map

Where the economy is missing a business

Because the organism records demand (every query, miss and question) and supply (every living, license-verified record and every coverage grant) in the same body, it can cross them. Every trade × state × city cell gets a demand count over 90 days, a living-supply count, demand-per-entity, a 30-day trend and a vacancy score of demand × 1/(supply+1), so high demand against zero supply ranks first. Cells where a business was quarantined or archived in the last 90 days carry a recent-death marker: that demand is now orphaned. Zero-supply cells are not conclusions — they leave as coverage_gap tasks on /api/public/crawl-tasks (id prefix vacancy:) so a worker can check whether supply exists and we simply have not seeded it; a cell that still has no living supply after a seeding attempt is promoted to a verified vacancy. The organism does not just record the economy — it locates where the economy is missing a business. Public teaser of the ten widest gaps: /vacancies.

POST/api/public/observe

Tell the organism it is wrong (open channel)

Every consumer is a sensor. Open, no auth, rate-limited per IP. Reports land in quarantine and never touch canonical data: a report is promoted into the observed stream (source field_report, and therefore into the changes feed) once 3+ independent reporters agree on the same value, or when an operator approves it in the admin Field Reports queue. Reporter reputation — submitted, confirmed, rejected — travels with your fingerprint.

POST /api/public/observe
curl -s -X POST "https://q-and-a.ai/api/public/observe" \
  -H 'content-type: application/json' \
  -d '{
    "entity_id": "ENTITY_UUID",
    "field": "phone",
    "reported_value": "(602) 555-0142",
    "evidence_note": "Answered at this number; the record number rang dead.",
    "reporter": "YourAgent/1.0"
  }'

# -> { "ok": true, "status": "quarantined", "corroborations": 1, "needed": 3,
#      "reporter_reputation": { "submitted": 4, "confirmed": 3, "rejected": 0 } }
GET/api/public/verify?entity_id={uuid}&sig={sig}

Signed records — prove your cache is still ours

Every entity response carries a signature block { alg, key_id, signed_at, sig } computed over the canonical record JSON with sorted keys. Cache a record, then re-verify later without re-downloading the whole payload. Phase 1 is HMAC-SHA256 over the SIGNING_KEY secret; until that secret is set, key_id reads dev-unset and must not be trusted. Phase 2 upgrades to asymmetric keys / DID for offline verification.

GET /api/public/verify
curl -s "https://q-and-a.ai/api/public/verify?entity_id=ENTITY_UUID&sig=SIG"

# -> { "valid": true, "key_id": "k1", "current_revision": 12, "signed_at": "…" }
# valid:false means the record moved on — read the changes feed.
GET/api/public/badge?entity_id={uuid}

Federation badge (JavaScript)

Claimed entities embed one script tag on their own site; it renders their live vitality dot and links back to the canonical record. Every render logs a demand event (kind badge_view), so a badge on someone else's domain still feeds the ledger here.

GET /api/public/badge
<script src="https://q-and-a.ai/api/public/badge?entity_id=ENTITY_UUID" async></script>
POST/api/public/experience

The experienced stream (call-tracking providers)

The third stream: not what the entity claims, not what the world observed — what actually happened on contact. Calls answered or missed, duration, repeat contacts, job outcomes. Rows appear as experienced[] on the record, carry 15 points of the trust score, and are written to the changes feed with stream experienced. Same bearer token as ingest. TODO: CALL_TRACKING_PROVIDER wiring.

POST /api/public/experience
curl -s -X POST "https://q-and-a.ai/api/public/experience" \
  -H 'authorization: Bearer $INGEST_API_KEY' \
  -H 'content-type: application/json' \
  -d '{
    "experiences": [
      { "entity_id": "ENTITY_UUID", "kind": "call_answered", "value": "18",
        "source": "call_tracking", "occurred_at": "2026-07-20T18:04:00Z" },
      { "entity_id": "ENTITY_UUID", "kind": "job_outcome", "value": "completed" }
    ]
  }'

Three streams, one record

declared is what the entity says, observed is what the world checked, and experienced is what happened when someone actually made contact. Trust is scored across all three: stream agreement 30, license registry 22, liveness 16, experience 15, freshness 9, self-declaration 8.

No query dies inside the body: ask an entity about a trade it does not handle and it refers you sideways, in first person, to a licensed sibling — with that entity's own record endpoint. The same graph is exposed as relations[] on every record (parent, subsidiary, brand, same_owner, nearby_same_trade, referral_partner).

Reading a reconciled field

Every entry in reconciled_fields carries an agreement marker:

  • agree — both streams say the same thing. Safe to act on.
  • divergent — the streams disagree; both values are included. Decide with your own policy.
  • declared_only — only the entity has spoken.
  • observed_only — only the world has spoken; the entity has never claimed itself.

Errors use plain JSON: 400 invalid_body, 401 unauthorized, 404 entity_not_found, 429 rate_limited, 503 ingest_not_configured.

Terms for machine consumers

Responses are served under Citation License 1.0 — cite with attribution welcomed; wholesale republication prohibited. Each response also carries an X-OrgAInism-License header. Prefer live calls over caching: a record's heartbeat is the point.

Pick an entity UUID from the directory →