Compass Food Technologies
DocsIntegration patterns

Docs

Integration patterns

Two chains cover almost every integration. Both end at the same place: a fit, not_fit, or unknown verdict from Decide.

Before the first call

Sign up and copy the key from the next screen. Send it as X-Compass-API-Key. Requests go to https://api.compassfoodtechnologies.com. Search, Enrich, and Decide are the core workflow. Changes and Refresh support synchronization; they are not substitutes for a verdict.

Your key opens the supported data endpoints. Allowance and rate limits cap how many calls you make. They do not change response fidelity. Use X-Compass-Mode: fast unless you need evidence fields. Rich costs more credits and returns those fields.

Pattern A — You already have restaurant records

This is the standard place-matching, or harmonization, flow. Enrich each record with name and address, or with google_place_id when you already store one. Do not send google_place_id to Decide.

1. Enrich the record. A confirmed match returns HTTP 200 with matched: true and a compass_id.

Abridged EnrichResponseMatched example — 9 of 84 contract leaves. Illustrative values, not a live payload.

{
  "compass_request_id": "req_example_green_bowl_enrich",
  "matched": true,
  "match_confidence": 0.97,
  "compass_id": "cmp_example_green_bowl_kitchen_001",
  "restaurant": {
    "compass_id": "cmp_example_green_bowl_kitchen_001",
    "name": "Green Bowl Kitchen",
    "vegan_score": {
      "overall": 82,
      "scoring_version": "v4.0-deterministic"
    }
  },
  "ai_generated": true
}

2. Store the mapping only after a confirmed match. Save that returned compass_id next to your own restaurant ID. Enrich once per record, not once per request. Read ID stability before treating a stored ID as permanent.

If the response is matched: false or compass_id is null, stop. Do not call Decide, and do not silently take the first match_candidates entry. Correct the identity you sent, or review the candidates yourself.

Abridged EnrichResponseUnmatched example — 8 of 17 contract leaves. Illustrative values, not a live payload.

{
  "compass_request_id": "req_example_green_bowl_unmatched",
  "matched": false,
  "match_confidence": 0.41,
  "compass_id": null,
  "match_candidates": [
    {
      "compass_id": "cmp_example_green_bowl_candidate",
      "name": "Green Bowl",
      "match_score": 0.62
    }
  ],
  "ai_generated": true
}

3. Decide with the stored ID. Pass the persisted compass_id and the user profile. Read the ID from the Enrich response; do not substitute a fictional ID.

Abridged DecisionResponse example — 9 of 26 contract leaves. Illustrative values, not a live payload.

{
  "compass_request_id": "req_example_green_bowl_decide",
  "compass_id": "cmp_example_green_bowl_kitchen_001",
  "decision": "unknown",
  "confidence": "low",
  "reason_codes": [
    "INSUFFICIENT_EVIDENCE"
  ],
  "risk_flags": [
    "limited_evidence"
  ],
  "recommended_user_text": "Verify details with the restaurant before visiting.",
  "verification_required": true,
  "ai_generated": true
}

unknown is a successful dietary verdict: Compass evaluated the restaurant and cannot confirm fit. A missing or unresolved ID is different. Decide then returns 404 with restaurant-not-found, and there is no verdict. Keep your original record, handle the unresolved ID, and re-resolve the mapping when you need a new one.

Pattern B — You need discovery

1. Search with a user profile. Results carry compass_id and a match_for_profile preview. Send the destination you already store: location coordinates, or place with country_code, region, and city, such as {"country_code":"US","region":"ID","city":"Boise"}. A non-US country_code returns coverage.status: out_of_coverage instead of results, so your app can show its own non-US experience.

Abridged SearchResponse example — 6 of 111 contract leaves. Illustrative values, not a live payload.

{
  "compass_request_id": "req_example_quickstart",
  "results": [
    {
      "compass_id": "cmp_example_green_bowl_kitchen_001",
      "name": "Green Bowl Kitchen",
      "match_for_profile": {
        "decision": "fit",
        "confidence": "high"
      }
    }
  ],
  "total_results": 1
}

2. Decide on the result the user selects. Pass that result's compass_id to Decide. The preview is not the verdict.

Bulk enrichment

There is no batch Enrich endpoint yet. Walk records sequentially and back off when Compass asks you to. Retry 429 rate_limit_exceeded and ip_rate_limit_exceeded only: honor Retry-After, add a short jitter, and stop at 4 attempts or 30 seconds, whichever comes first. Stop immediately on monthly_limit_exceeded — another 429 will not create allowance. Stop on validation and auth failures, including 401, 403 provisional_key_expired, and revoked or suspended keys. Do not replay a timeout after the request was sent; that attempt is ambiguous.

const MAX_ATTEMPTS = 4;
const MAX_ELAPSED_MS = 30000;
const JITTER_MS = 250;
const baseUrl = "https://api.compassfoodtechnologies.com";
const apiKey = process.env.COMPASS_API_KEY;
if (!apiKey) throw new Error('Set COMPASS_API_KEY');

const started = Date.now();

async function compassCall(path, body, extraHeaders = {}) {
  let attempts = 0;
  while (attempts < MAX_ATTEMPTS) {
    if (Date.now() - started > MAX_ELAPSED_MS) throw new Error('stop: elapsed bound');
    attempts += 1;
    let response;
    try {
      response = await fetch(baseUrl + path, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Compass-API-Key': apiKey,
          ...extraHeaders,
        },
        body: JSON.stringify(body),
        signal: AbortSignal.timeout(10000),
      });
    } catch (error) {
      if (error?.name === 'TimeoutError' || error?.name === 'AbortError') {
        throw new Error('stop: ambiguous timeout after dispatch');
      }
      continue;
    }
    if (response.status === 429) {
      const problem = await response.json();
      if (problem.error === 'monthly_limit_exceeded') throw new Error('stop: allowance exhausted');
      if (problem.error !== 'rate_limit_exceeded' && problem.error !== 'ip_rate_limit_exceeded') {
        throw new Error('stop: unexpected 429');
      }
      if (attempts >= MAX_ATTEMPTS) throw new Error('stop: rate limit attempts exhausted');
      const retryAfter = Number(response.headers.get('Retry-After') ?? '1');
      const waitMs = Math.max(0, retryAfter) * 1000 + Math.floor(Math.random() * JITTER_MS);
      if (Date.now() + waitMs - started > MAX_ELAPSED_MS) throw new Error('stop: elapsed bound');
      await new Promise((resolve) => setTimeout(resolve, waitMs));
      continue;
    }
    if (response.status === 401 || response.status === 403) throw new Error('stop: auth');
    if (response.status === 400 || response.status === 422) throw new Error('stop: validation');
    if (response.status === 404) {
      const problem = await response.json();
      if (problem.error === 'restaurant-not-found') {
        throw new Error('stop: unresolved id; keep your record and re-resolve');
      }
      throw new Error('stop: not found');
    }
    if (!response.ok) throw new Error('stop: unexpected status ' + response.status);
    return response.json();
  }
  throw new Error('stop: attempt bound');
}

const enrich = await compassCall('/v1/enrich/restaurant', {
  name: 'Green Bowl Kitchen',
  address: '123 Example Street, Brooklyn, NY',
});
if (enrich.matched !== true || typeof enrich.compass_id !== 'string' || !enrich.compass_id) {
  console.log('No confirmed match. Do not call Decide. Review candidates instead of taking the first one.');
  console.log(enrich.match_candidates ?? []);
  process.exit(2);
}

// Persist only the returned compass_id, next to your own restaurant id.
const persistedId = enrich.compass_id;
const verdict = await compassCall('/v1/decision/restaurant-fit', {
  compass_id: persistedId,
  user_profile: { diet: 'strict_vegan' },
}, { 'X-Compass-Mode': 'rich' });
console.log(verdict.decision);

The same stop rules apply to one record or a loop. After a confirmed match, persist the returned ID before Decide. See Quickstart for signup, and the Enrich and Decide references for the fields this page abbreviates.

Integration Patterns | Compass Food Technologies