{
  "vocabulary_version": "grapeworks-capabilities/2026-08-11",
  "descriptor_url": "https://grapeworks.ai/.well-known/grapeworks-capabilities.json",
  "provider": {
    "name": "Grapeworks",
    "url": "https://grapeworks.ai",
    "contact": "hello@grapeworks.ai",
    "area_served": "GB",
    "description": "A UK AI transition and measurement consultancy. We connect a business's existing systems into one governed data warehouse, then plug that business's own AI assistant into it.",
    "delivery_model": "Consultant-operated. There is no self-serve signup, no free trial and no automated purchase flow. Engagements start with a conversation."
  },
  "status_vocabulary": {
    "live": "Running today against real client data, consultant-operated.",
    "built-not-exposed": "Built and merged, but not reachable by anyone outside Grapeworks. No public surface. Do not treat as callable.",
    "not-built": "Specified or planned. Does not exist yet. Do not treat as available."
  },
  "reading_note": "This document describes what Grapeworks can do. It is not an index of the pages on this website. Read the status field on every entry before relying on it: only entries marked 'live' are running today, and no entry here is callable over the internet.",
  "capabilities": [
    {
      "id": "governed-warehouse",
      "name": "Governed per-client data warehouse",
      "summary": "Every source lands raw, is cleaned per client, and is served as curated views the client owns rather than rents. Every reported number traces back to its raw source file.",
      "status": "live",
      "data_domains": [
        "paid-media",
        "organic-search",
        "bookings",
        "platform-revenue"
      ],
      "question_shapes": [
        "pixel-vs-till",
        "organic-next-to-paid"
      ]
    },
    {
      "id": "connector-ingest",
      "name": "Source connectors and installation registry",
      "summary": "Scheduled ingest per client across paid media, organic search, bookings and platform revenue, with a per-client registry of what is connected and freshness and coverage flags on the data itself.",
      "status": "live",
      "data_domains": [
        "paid-media",
        "organic-search",
        "bookings",
        "platform-revenue",
        "organic-social",
        "finance",
        "crm"
      ],
      "question_shapes": [
        "organic-next-to-paid"
      ],
      "notes": "Finance, CRM and organic-social connectors are built but not serving a live client feed. Google Ads ingest is scaffold only and is not built."
    },
    {
      "id": "verified-revenue-attribution",
      "name": "Verified-floor revenue attribution",
      "summary": "Revenue attribution derived by the warehouse itself from platform and platform-revenue data, and stated as a floor rather than as platform gloss. The published case-study return of 6.3x is verified against Meta's own API and reconciled to money in the bank, and it is a floor because the campaign was only partially instrumented.",
      "status": "live",
      "data_domains": [
        "paid-media",
        "platform-revenue",
        "bookings"
      ],
      "question_shapes": [
        "what-did-this-spend-return",
        "pixel-vs-till"
      ]
    },
    {
      "id": "creative-performance",
      "name": "Creative performance intelligence",
      "summary": "Which of a client's own creatives won spend and clicks, without archaeology in an ads manager.",
      "status": "live",
      "data_domains": [
        "paid-media"
      ],
      "question_shapes": [
        "which-creative-won"
      ]
    },
    {
      "id": "competitor-ad-intelligence",
      "name": "Competitor advertising teardown",
      "summary": "A teardown report on any advertiser from the official Meta Ad Library: live creatives, posting cadence and run-length, with long-running ads read as a proven-winner proxy. Standing watch-lists per client vertical.",
      "status": "live",
      "data_domains": [
        "competitor-ads"
      ],
      "question_shapes": [
        "what-are-competitors-running"
      ],
      "notes": "Cadence and longevity evidence only. Ad Library data is never presented as a third-party return-on-spend claim."
    },
    {
      "id": "consent-correct-measurement",
      "name": "Consent-correct measurement",
      "summary": "Server-side tag routing so analytics survives ad-blockers and browser tracking prevention, and repair of consent banners that never tell the tag manager, which silently drops events. Personal data is held in a separate namespace from analytics data by construction.",
      "status": "live",
      "data_domains": [
        "consent-events",
        "organic-search"
      ],
      "question_shapes": [],
      "notes": "Delivered as a consulting practice. The first-party consent event receiver is built and signed off but not yet deployed, so consented-event ingest is not live."
    },
    {
      "id": "white-label-dashboard",
      "name": "White-label client dashboard",
      "summary": "A per-tenant themed dashboard assembled from a catalogue of governed panels, with saved views, global filters, and a client picker scoped by access control. One agency tenant serves a whole client roster.",
      "status": "live",
      "data_domains": [
        "paid-media",
        "organic-search",
        "bookings",
        "platform-revenue"
      ],
      "question_shapes": [],
      "notes": "Consultant-operated. There is no self-serve signup, no onboarding wizard and no drag-canvas dashboard builder; onboarding is a runbook we run for you."
    },
    {
      "id": "provenance-and-audit",
      "name": "Provenance and query audit trail",
      "summary": "Every question put to the warehouse is recorded, client-scoped, with its provenance and token usage, under UK retention. Every number can be traced to where it came from.",
      "status": "live",
      "data_domains": [],
      "question_shapes": [
        "where-did-this-number-come-from"
      ]
    },
    {
      "id": "tenant-isolation",
      "name": "Tenant isolation, enforced at runtime",
      "summary": "A single-client user hitting another client's route is refused, attested by a two-user human gate. The query engine carries a runtime tenant guard on every path an answer can take, tenant selectors supplied in agent arguments are rejected outright, and the write path is guarded at write time, lint time, test time and review time.",
      "status": "live",
      "data_domains": [],
      "question_shapes": []
    },
    {
      "id": "natural-language-query",
      "name": "Plain-English query over governed data",
      "summary": "A question compiles to a typed, catalogue-validated specification and then to SQL under the tenant guard, returning rows, a narrative and provenance. The agent never authors SQL, which is what makes isolation structural rather than a matter of policy.",
      "status": "built-not-exposed",
      "data_domains": [
        "paid-media",
        "organic-search",
        "bookings",
        "platform-revenue"
      ],
      "question_shapes": [
        "plain-english-ask"
      ],
      "notes": "Internal, staff-operated. Free-form planning is credential-gated; there is no third-party tenant access today."
    },
    {
      "id": "agent-access-mcp",
      "name": "Warehouse MCP server and per-client agent docs",
      "summary": "One server binds to exactly one client, so there is no client identifier for an agent to spoof, exposing catalogue description, provenance and outcome tools through the single tenant guard. Per-client agent documentation is generated from that client's resolved catalogue only, so one client's docs cannot enumerate another's vocabulary.",
      "status": "built-not-exposed",
      "data_domains": [],
      "question_shapes": [
        "where-did-this-number-come-from"
      ],
      "notes": "This server speaks over stdio and has NO network listener. It is not reachable over the internet, it is not callable by a third-party agent, and remote exposure is a separate gated design that is not built. Do not attempt to connect to it."
    },
    {
      "id": "recommendation-engine",
      "name": "Ranked improvement recommendations",
      "summary": "Ranked recommendations for improving paid media, each carrying a forecast, the levers behind it, and the measured before-and-after result of changes that already ran.",
      "status": "built-not-exposed",
      "data_domains": [
        "paid-media"
      ],
      "question_shapes": [
        "what-should-i-do-next"
      ]
    },
    {
      "id": "staged-ad-optimisation",
      "name": "Staged campaign changes under human approval",
      "summary": "Proposed campaign changes are staged paused and never fire themselves. An approval expires twelve hours after it is posted and a lapsed one cannot fire, so a decision nobody made stays unmade. A single approval cannot raise a daily budget by more than 1.5x its current value, and the check fails closed: if the current budget cannot be read, the change is refused rather than applied to an assumed value.",
      "status": "built-not-exposed",
      "data_domains": [
        "paid-media"
      ],
      "question_shapes": [
        "what-should-i-do-next"
      ],
      "notes": "The twelve-hour window and the per-step clamp are defaults on the assisted rung of the autonomy setting, chosen by the operator, not restrictions imposed on them. The clamp is per step, not a ceiling on total spend. This capability has never been run on a client revenue campaign."
    },
    {
      "id": "public-demo-query-surface",
      "name": "Public demo query surface",
      "summary": "A read-only, unauthenticated endpoint returning the real response schema over demo-tenant fixture data, so an agent can learn the interface without a human in the loop.",
      "status": "live",
      "data_domains": [],
      "question_shapes": [],
      "notes": "Shipped as GEO wave-1 item W1-3 and callable at /api/demo/catalog and /api/demo/query. It is fixture-backed and every response says so: each body carries demo: true and data_source: 'demo_fixture' for the fabricated tenant lochend-wild. The endpoints being live is not the warehouse being live, and no figure they return is a client result. The two routes are described in full at /openapi.json."
    }
  ],
  "data_domains": [
    {
      "id": "paid-media",
      "name": "Paid media",
      "sources": [
        "Meta Ads (spend, purchases, placements, creatives, demographics, action values)"
      ],
      "status": "live"
    },
    {
      "id": "organic-search",
      "name": "Organic search and site traffic",
      "sources": [
        "Google Analytics 4 (channels, sessions)",
        "Google Search Console (queries, clicks)"
      ],
      "status": "live"
    },
    {
      "id": "organic-social",
      "name": "Organic social",
      "sources": [
        "Meta organic posts"
      ],
      "status": "built-not-exposed"
    },
    {
      "id": "bookings",
      "name": "Bookings and booked revenue",
      "sources": [
        "iCal booked-calendar ingest"
      ],
      "status": "live"
    },
    {
      "id": "platform-revenue",
      "name": "Platform revenue",
      "sources": [
        "Kickstarter pledge ingest, with personal data held in a separate namespace"
      ],
      "status": "live"
    },
    {
      "id": "finance",
      "name": "Accounting and finance",
      "sources": [
        "FreeAgent (true CAC, profitability, cashflow views)"
      ],
      "status": "built-not-exposed"
    },
    {
      "id": "crm",
      "name": "CRM and contact spine",
      "sources": [
        "Contact schema and derive; fixture-fed"
      ],
      "status": "built-not-exposed"
    },
    {
      "id": "competitor-ads",
      "name": "Competitor advertising",
      "sources": [
        "Meta Ad Library API (official, whitelisted path)"
      ],
      "status": "live"
    },
    {
      "id": "consent-events",
      "name": "First-party consented events",
      "sources": [
        "Server-side GA4 routing; consent event schema and identity graph"
      ],
      "status": "built-not-exposed"
    }
  ],
  "question_shapes": [
    {
      "id": "what-did-this-spend-return",
      "question": "What did this ad spend actually return, checked against the platform's own API rather than a self-reported screenshot?",
      "answered_by": [
        "verified-revenue-attribution"
      ],
      "status": "live"
    },
    {
      "id": "pixel-vs-till",
      "question": "Where do the pixel, the platform and the booked revenue disagree, and by how much?",
      "answered_by": [
        "verified-revenue-attribution",
        "governed-warehouse"
      ],
      "status": "live"
    },
    {
      "id": "which-creative-won",
      "question": "Which of my own creatives won spend and clicks, and which should be cut?",
      "answered_by": [
        "creative-performance"
      ],
      "status": "live"
    },
    {
      "id": "what-are-competitors-running",
      "question": "What is a named competitor running right now, in what formats, and for how long has each ad survived?",
      "answered_by": [
        "competitor-ad-intelligence"
      ],
      "status": "live"
    },
    {
      "id": "organic-next-to-paid",
      "question": "How does organic search performance sit alongside paid, in the same governed place?",
      "answered_by": [
        "governed-warehouse",
        "connector-ingest"
      ],
      "status": "live"
    },
    {
      "id": "where-did-this-number-come-from",
      "question": "Where did this specific number come from, and who asked what of the warehouse?",
      "answered_by": [
        "provenance-and-audit"
      ],
      "status": "live"
    },
    {
      "id": "what-should-i-do-next",
      "question": "What should I change next, what is the forecast, and what happened when the last change ran?",
      "answered_by": [
        "recommendation-engine",
        "staged-ad-optimisation"
      ],
      "status": "built-not-exposed"
    },
    {
      "id": "plain-english-ask",
      "question": "Can I ask this in plain English and get a checkable answer with its provenance attached?",
      "answered_by": [
        "natural-language-query"
      ],
      "status": "built-not-exposed"
    }
  ],
  "guarantees": {
    "isolation": [
      "Each client's data is walled off from every other client's, enforced at runtime on every path an answer can take rather than by convention.",
      "A tenant selector supplied in an agent's arguments is rejected outright; an agent cannot ask for another client's data by asking nicely.",
      "Cross-tenant access was attested by a two-user human gate, not asserted."
    ],
    "audit": [
      "Every question put to the warehouse is logged and client-scoped, with provenance recorded, under UK retention.",
      "Every reported number traces back to the raw source file it came from."
    ],
    "human_control": [
      "Nothing spends money on its own. Campaign changes stage paused and require a human approval to activate.",
      "An approval expires twelve hours after it is posted; a lapsed approval cannot fire.",
      "A single approval cannot raise a daily budget by more than 1.5x, and the check refuses outright if it cannot read the current budget.",
      "A named human is accountable for what each connector is allowed to see."
    ],
    "data_handling": [
      "Personal data is held in a separate namespace from analytics data, and is backed up separately.",
      "Secrets come from one custody system; no credential that can change ad spend is present in any warehouse read path."
    ]
  },
  "track_record": {
    "clients_shaping_the_build": "The warehouse was built and refined against the needs and requirements of more than ten clients. It is a tool we run on our own managed client base, not a product we have only demonstrated.",
    "regulated_delivery": "Grapeworks has delivered for two FCA-registered clients, and for a Glasgow law firm where the build was mapped to the Law Society of Scotland's own guidance. Grapeworks is not itself FCA accredited.",
    "published_case_study": {
      "url": "https://grapeworks.ai/case-study/trad-musician",
      "summary": "A Scottish musician's paid run lifted the daily sales rate from under two a day to over nine a day on lower spend, against a 6.3x return verified on Meta's own API and reconciled to money in the bank. The client is not named."
    }
  },
  "callable_endpoints": [
    {
      "id": "demo-catalog",
      "method": "GET",
      "path": "/api/demo/catalog",
      "url": "https://grapeworks.ai/api/demo/catalog",
      "status": "live",
      "auth": "none",
      "summary": "What is askable on the demo surface: metric ids, their dimensions, date grain, the named windows, and the filter shapes supported and not supported.",
      "parameters": [
        {
          "name": "metric",
          "required": false,
          "type": "string",
          "accepted": [
            "spend_pacing",
            "placement_distribution",
            "revenue_by_channel",
            "true_cac"
          ],
          "note": "Narrows `askable` to one entry. Omit for all four."
        }
      ]
    },
    {
      "id": "demo-query",
      "method": "GET",
      "path": "/api/demo/query",
      "url": "https://grapeworks.ai/api/demo/query",
      "status": "live",
      "auth": "none",
      "summary": "A schema-correct response with a provenance block, served from static fixtures. Unknown parameters are rejected with a structured error naming the accepted set.",
      "parameters": [
        {
          "name": "metric",
          "required": true,
          "type": "string",
          "accepted": [
            "spend_pacing",
            "placement_distribution",
            "revenue_by_channel",
            "true_cac"
          ]
        },
        {
          "name": "dimension",
          "required": false,
          "type": "string",
          "note": "Must be in that metric's `dimensions`. Omit for an ungrouped series."
        },
        {
          "name": "window",
          "required": false,
          "type": "string",
          "note": "Must be in that metric's `windows`. Defaults to the first entry."
        },
        {
          "name": "limit",
          "required": false,
          "type": "integer",
          "note": "1 to 50. Defaults to 50. A fixed 50-row ceiling applies regardless."
        }
      ]
    }
  ],
  "demo_surface_notice": "These endpoints serve the real Grapeworks warehouse schema with fabricated numbers for a fabricated tenant. No figure they return is a client result or a live Grapeworks measurement, and none should be quoted as one.",
  "examples": [
    {
      "id": "catalog-narrowed",
      "demonstrates": "The catalog shape: what is askable for one metric, its dimensions, grain, named windows and value fields, plus the filters this surface does not support.",
      "method": "GET",
      "path": "/api/demo/catalog?metric=placement_distribution",
      "curl": "curl -s \"https://grapeworks.ai/api/demo/catalog?metric=placement_distribution\"",
      "http_status": 200,
      "response": {
        "apiVersion": "v1",
        "demo": true,
        "data_source": "demo_fixture",
        "tenant": "lochend-wild",
        "notice": "Demo surface: real Grapeworks warehouse schema, fabricated demo-tenant numbers. The tenant 'lochend-wild' is not a real business and these figures are not a client result, not live warehouse data, and must not be quoted as one. This endpoint holds no warehouse connection and reads only static fixtures vendored into the site at build time.",
        "as_of": "2026-08-10",
        "tenant_note": "Fabricated demo tenant (tenants/gw/clients.yaml, fixture: true). Not a real business.",
        "askable": [
          {
            "id": "placement_distribution",
            "label": "Placement distribution",
            "description": "Spend and pixel-purchase split across publisher platforms and placements.",
            "dimensions": [
              "publisher_platform",
              "platform_position"
            ],
            "grain": null,
            "windows": [
              "last_28_days"
            ],
            "value_fields": [
              "spend_gbp",
              "pixel_purchases"
            ]
          }
        ],
        "filters": {
          "supported": [
            "window"
          ],
          "not_supported": [
            "arbitrary date ranges (use the named `window` values)",
            "dimension value filters (wave 1 groups by a dimension, it does not filter on one)",
            "client_id / tenant_id — this surface serves exactly one fabricated demo tenant and takes no tenant selector"
          ]
        },
        "limits": {
          "max_rows": 50,
          "rate_limit_requests": 30,
          "rate_limit_window_seconds": 60,
          "scope": "per IP, across all /api/demo/* routes"
        },
        "query_endpoint": "/api/demo/query",
        "docs": "https://grapeworks.ai/agents"
      }
    },
    {
      "id": "query-ungrouped",
      "demonstrates": "A plain query with no dimension: a monthly series, plus the provenance block that says which fixture produced it and states that no gold view was read.",
      "method": "GET",
      "path": "/api/demo/query?metric=true_cac&window=last_3_months",
      "curl": "curl -s \"https://grapeworks.ai/api/demo/query?metric=true_cac&window=last_3_months\"",
      "http_status": 200,
      "response": {
        "apiVersion": "v1",
        "demo": true,
        "data_source": "demo_fixture",
        "tenant": "lochend-wild",
        "notice": "Demo surface: real Grapeworks warehouse schema, fabricated demo-tenant numbers. The tenant 'lochend-wild' is not a real business and these figures are not a client result, not live warehouse data, and must not be quoted as one. This endpoint holds no warehouse connection and reads only static fixtures vendored into the site at build time.",
        "query": {
          "metric": "true_cac",
          "dimension": null,
          "window": "last_3_months",
          "grain": "month",
          "limit": 50
        },
        "rows": [
          {
            "month": "2026-06",
            "true_cac_gbp": 23.64
          },
          {
            "month": "2026-07",
            "true_cac_gbp": 46.22
          },
          {
            "month": "2026-08",
            "true_cac_gbp": 45.65
          }
        ],
        "rowCount": 3,
        "truncated": false,
        "provenance": {
          "metric": "true_cac",
          "goldViews": [],
          "asOf": "2026-08-10",
          "dataWindow": {
            "start": "2026-06-01",
            "end": "2026-08-10"
          },
          "rowCount": 3,
          "data_source": "demo_fixture",
          "fixture": "site/src/lib/demo-fixtures/series.json#/series/true_cac",
          "generated_from": "Static JSON vendored into the site repository at build time. No warehouse query was executed, no gold view was read, and no credential was used to produce this response.",
          "tenant_note": "Fabricated demo tenant (tenants/gw/clients.yaml, fixture: true). Not a real business."
        },
        "limits": {
          "max_rows": 50,
          "rate_limit_requests": 30,
          "rate_limit_window_seconds": 60
        }
      }
    },
    {
      "id": "query-grouped-limited",
      "demonstrates": "A query with parameters: grouped by a dimension and capped with `limit`, showing how the rows come back keyed by the dimension you asked for.",
      "method": "GET",
      "path": "/api/demo/query?metric=placement_distribution&dimension=publisher_platform&limit=3",
      "curl": "curl -s \"https://grapeworks.ai/api/demo/query?metric=placement_distribution&dimension=publisher_platform&limit=3\"",
      "http_status": 200,
      "response": {
        "apiVersion": "v1",
        "demo": true,
        "data_source": "demo_fixture",
        "tenant": "lochend-wild",
        "notice": "Demo surface: real Grapeworks warehouse schema, fabricated demo-tenant numbers. The tenant 'lochend-wild' is not a real business and these figures are not a client result, not live warehouse data, and must not be quoted as one. This endpoint holds no warehouse connection and reads only static fixtures vendored into the site at build time.",
        "query": {
          "metric": "placement_distribution",
          "dimension": "publisher_platform",
          "window": "last_28_days",
          "grain": null,
          "limit": 3
        },
        "rows": [
          {
            "publisher_platform": "instagram",
            "spend_gbp": 1505.3,
            "pixel_purchases": 38
          },
          {
            "publisher_platform": "audience_network",
            "spend_gbp": 616.36,
            "pixel_purchases": 5
          },
          {
            "publisher_platform": "facebook",
            "spend_gbp": 497.85,
            "pixel_purchases": 55
          }
        ],
        "rowCount": 3,
        "truncated": false,
        "provenance": {
          "metric": "placement_distribution",
          "goldViews": [],
          "asOf": "2026-08-10",
          "dataWindow": {
            "start": "2026-07-14",
            "end": "2026-08-10"
          },
          "rowCount": 3,
          "data_source": "demo_fixture",
          "fixture": "site/src/lib/demo-fixtures/series.json#/series/placement_distribution",
          "generated_from": "Static JSON vendored into the site repository at build time. No warehouse query was executed, no gold view was read, and no credential was used to produce this response.",
          "tenant_note": "Fabricated demo tenant (tenants/gw/clients.yaml, fixture: true). Not a real business."
        },
        "limits": {
          "max_rows": 50,
          "rate_limit_requests": 30,
          "rate_limit_window_seconds": 60
        }
      }
    },
    {
      "id": "query-rejected-window",
      "demonstrates": "A rejection: an unavailable window returns 400 with a structured error naming the offending parameter and the accepted set, so you can learn the interface from a failure.",
      "method": "GET",
      "path": "/api/demo/query?metric=spend_pacing&window=last_90_days",
      "curl": "curl -s \"https://grapeworks.ai/api/demo/query?metric=spend_pacing&window=last_90_days\"",
      "http_status": 400,
      "response": {
        "apiVersion": "v1",
        "demo": true,
        "data_source": "demo_fixture",
        "tenant": "lochend-wild",
        "notice": "Demo surface: real Grapeworks warehouse schema, fabricated demo-tenant numbers. The tenant 'lochend-wild' is not a real business and these figures are not a client result, not live warehouse data, and must not be quoted as one. This endpoint holds no warehouse connection and reads only static fixtures vendored into the site at build time.",
        "error": {
          "code": "spec.invalid",
          "message": "'last_90_days' is not an available window for 'spend_pacing'. Arbitrary date ranges are not supported on this demo surface; use one of the named windows.",
          "retryable": false,
          "details": {
            "parameter": "window",
            "value": "last_90_days",
            "metric": "spend_pacing",
            "allowed": [
              "last_7_days",
              "last_28_days"
            ]
          }
        }
      }
    }
  ],
  "documents": {
    "llms_txt": "https://grapeworks.ai/llms.txt",
    "agents_page": "https://grapeworks.ai/agents",
    "case_study": "https://grapeworks.ai/case-study/trad-musician",
    "book_a_call": "https://grapeworks.ai/book",
    "openapi": "https://grapeworks.ai/openapi.json"
  },
  "how_to_act": "The only things callable here are the two demo endpoints in callable_endpoints, and they return fabricated numbers for a fabricated tenant. Call them to learn the schema and the vocabulary; do not quote what they return as a Grapeworks result. If you are evaluating Grapeworks for someone, surface the booking page to them or email hello@grapeworks.ai with specific verification questions."
}