{"schema_version":1,"generated_at":"2026-10-29T17:28:49.777Z","principle":"Agentry is the agent-first control plane over a customer-owned PostHog Cloud project: read-only OAuth analytics and typed resource reads, source-backed onboarding, trusted deploy attribution, bounded evidence, and scoped automation coordination. Applications send telemetry and source maps directly to PostHog.","discovery":{"reference":"https://raw.githubusercontent.com/agentry-sh/agentry/main/AGENTRY.md","install_bootstrap":"https://raw.githubusercontent.com/agentry-sh/agentry/main/INSTALL.md","setup_manifest":"/v1/setup/manifest","openapi_schema":"/v1/openapi.json","filtered_schema_examples":["/v1/openapi.json?index=true","/v1/openapi.json?flow=onboarding","/v1/openapi.json?flow=daily_use","/v1/openapi.json?flow=posthog","/v1/openapi.json?flow=automation","/v1/openapi.json?tag=Feature%20Flags","/v1/openapi.json?tag=Onboarding&include_components=false","/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fonboarding&method=post"]},"auth_types":{"none":"Public discovery, query blueprints, the onboarding source-snapshot helper, schema, and privacy disclosure.","api_key":"Private agentry_sk_ key for owner/member read and mutation endpoints.","ci_deploy":"Trusted CI agentry_ci_ credential for Agentry deploy evidence and provider-observed automation PR proof only. PostHog source-map credentials remain customer-owned.","automation_runner":"Exact-bound agentry_runner_ credential for one saved automation_id and fixed trigger/claim/run/approval-request scopes.","public_query_url":"Browser-safe unguessable publication URLs returned by POST /v1/projects/:id/public-queries. The complete URL is the visitor capability; there is no second query-string key.","admin":"Operator-only plan and feedback administration.","stripe_webhook":"Stripe-signed webhook delivery for subscription lifecycle events."},"surfaces":{"http":{"schema_url":"/v1/openapi.json","schema_index_url":"/v1/openapi.json?index=true","setup_manifest_url":"/v1/setup/manifest","project_setup_url":"/v1/projects/{project_id}/setup","note":"OpenAPI is canonical for required/optional fields, auth, examples, and responses."},"query_blueprints":{"catalog_url":"/v1/query-blueprints","get_url":"/v1/query-blueprints/{blueprint_id}","run_url":"/v1/projects/{project_id}/query-blueprints/{blueprint_id}/run","count":21,"categories":["deploys","errors","events","funnels","retention","users"],"examples":[{"id":"active_users_daily","kind":"query_blueprint","title":"Daily Active Users (last N days)","description":"Count of unique distinct_ids per day from analytics events.","category":"users","backend":"analytics","publication_safety":{"status":"safe_aggregate","public_access":"allowed","output_granularity":"aggregate","identifier_columns":[],"accepts_arbitrary_segment_property":false,"reason":"Returns aggregate analytics only; it exposes no person, account, or session identifiers and cannot select an arbitrary segment property."},"params":[{"name":"days","type":"number","description":"Lookback window in days","default":14}],"query":"SELECT toDate(timestamp) AS day, count(DISTINCT distinct_id) AS dau\nFROM events\nWHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n  AND timestamp > now() - INTERVAL {{days}} DAY\nGROUP BY day\nORDER BY day","expected_columns":["day","dau"],"render_hint":{"type":"line","x":"day","y":"dau","title":"Daily Active Users","notes":["DAU counts unique distinct_id per day. Anonymous users count if they generated any event."]},"example_user_question":"how many active users did we have each day this week?","endpoints":{"run":"/v1/projects/:project_id/query-blueprints/active_users_daily/run","publish":"/v1/projects/:project_id/public-queries"},"publishable":true,"next_action":"Before running this analytics template for a product answer, compare its params with the saved signal map, current verified onboarding receipt, event names, and property keys. Query blueprints are executable templates, not answer-readiness proof. Blueprint defaults are examples, not proof of the customer's funnel; pass approved events/properties as params. If parameterized_required_event_properties is present, resolve the named params first and verify that exact event/property pair with event-property-keys. Publish with POST /v1/projects/:project_id/public-queries only when the user asked for browser-safe reports or dashboards."},{"id":"users_by_cohort_signup_week","kind":"query_blueprint","title":"Users by signup-week cohort","description":"Groups users by the week they first signed up; useful for cohort sizing.","category":"users","backend":"analytics","publication_safety":{"status":"safe_aggregate","public_access":"allowed","output_granularity":"aggregate","identifier_columns":[],"accepts_arbitrary_segment_property":false,"reason":"Returns aggregate analytics only; it exposes no person, account, or session identifiers and cannot select an arbitrary segment property."},"params":[{"name":"weeks","type":"number","description":"Lookback in weeks","default":12},{"name":"signup_event","type":"string","description":"The event that marks signup. Choose this from the saved signal map or observed event names.","required":true}],"query":"SELECT\n  cohort_week,\n  count() AS users\nFROM (\n  SELECT distinct_id, toMonday(first_seen_at) AS cohort_week\n  FROM (\n    SELECT distinct_id, min(timestamp) AS first_seen_at\n    FROM events\n    WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n      AND event = '{{signup_event}}'\n      AND timestamp > now() - INTERVAL {{weeks}} WEEK\n    GROUP BY distinct_id\n  )\n)\nGROUP BY cohort_week\nORDER BY cohort_week","expected_columns":["cohort_week","users"],"render_hint":{"type":"bar","x":"cohort_week","y":"users","title":"New users per signup-week cohort"},"example_user_question":"show me users grouped by the week they signed up","endpoints":{"run":"/v1/projects/:project_id/query-blueprints/users_by_cohort_signup_week/run","publish":"/v1/projects/:project_id/public-queries"},"publishable":true,"next_action":"Before running this analytics template for a product answer, compare its params with the saved signal map, current verified onboarding receipt, event names, and property keys. Query blueprints are executable templates, not answer-readiness proof. Blueprint defaults are examples, not proof of the customer's funnel; pass approved events/properties as params. If parameterized_required_event_properties is present, resolve the named params first and verify that exact event/property pair with event-property-keys. Publish with POST /v1/projects/:project_id/public-queries only when the user asked for browser-safe reports or dashboards."},{"id":"weekly_retention","kind":"query_blueprint","title":"Weekly retention by signup cohort","description":"For each signup-week cohort, the % of users who performed the chosen product activity event in week 1, 2, 3, ...","category":"retention","backend":"analytics","publication_safety":{"status":"safe_aggregate","public_access":"allowed","output_granularity":"aggregate","identifier_columns":[],"accepts_arbitrary_segment_property":false,"reason":"Returns aggregate analytics only; it exposes no person, account, or session identifiers and cannot select an arbitrary segment property."},"params":[{"name":"weeks","type":"number","description":"How many cohort weeks to compute","default":8},{"name":"signup_event","type":"string","description":"Cohort-defining event. Choose this from the saved signal map or observed event names.","required":true},{"name":"activity_event","type":"string","description":"The event that proves a user returned or got value. Choose an approved product activity event from the signal map; do not use background/server noise.","required":true}],"query":"WITH signups AS (\n  SELECT distinct_id, cohort\n  FROM (\n    SELECT distinct_id, toMonday(first_seen_at) AS cohort\n    FROM (\n      SELECT distinct_id, min(timestamp) AS first_seen_at\n      FROM events\n      WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n        AND event = '{{signup_event}}'\n        AND timestamp > now() - INTERVAL {{weeks}} WEEK\n      GROUP BY distinct_id\n    )\n  )\n),\nactivity AS (\n  SELECT distinct_id, active_week\n  FROM (\n    SELECT distinct_id, toMonday(timestamp) AS active_week\n    FROM events\n    WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n      AND event = '{{activity_event}}'\n      AND timestamp > now() - INTERVAL {{weeks}} WEEK\n    GROUP BY distinct_id, active_week\n  )\n),\ncohort_sizes AS (\n  SELECT cohort, count(DISTINCT distinct_id) AS cohort_size\n  FROM signups\n  GROUP BY cohort\n)\nSELECT\n  s.cohort AS cohort_week,\n  dateDiff('week', s.cohort, a.active_week) AS week_offset,\n  cs.cohort_size AS cohort_size,\n  count(DISTINCT s.distinct_id) AS retained_users,\n  round(100.0 * count(DISTINCT s.distinct_id) / nullIf(cs.cohort_size, 0), 2) AS retention_pct\nFROM signups s\nJOIN activity a ON s.distinct_id = a.distinct_id\nJOIN cohort_sizes cs ON cs.cohort = s.cohort\nWHERE a.active_week >= s.cohort\nGROUP BY cohort_week, week_offset, cohort_size\nORDER BY cohort_week, week_offset","expected_columns":["cohort_week","week_offset","cohort_size","retained_users","retention_pct"],"render_hint":{"type":"stacked_bar","x":"cohort_week","y":"retention_pct","title":"Weekly retention percentage by cohort","notes":["week_offset=0 is the cohort itself. retention_pct is retained_users divided by cohort_size.","activity_event defines retention. Pick a source-backed product activity event from the saved signal map instead of counting any event.","Render as a triangle/cohort heatmap if your UI supports it; markdown table is fine otherwise."]},"example_user_question":"what does our weekly retention look like by cohort?","example_params":{"weeks":8,"signup_event":"signup_completed","activity_event":"activation_completed"},"endpoints":{"run":"/v1/projects/:project_id/query-blueprints/weekly_retention/run","publish":"/v1/projects/:project_id/public-queries"},"publishable":true,"next_action":"Before running this analytics template for a product answer, compare its params with the saved signal map, current verified onboarding receipt, event names, and property keys. Query blueprints are executable templates, not answer-readiness proof. Blueprint defaults are examples, not proof of the customer's funnel; pass approved events/properties as params. If parameterized_required_event_properties is present, resolve the named params first and verify that exact event/property pair with event-property-keys. Publish with POST /v1/projects/:project_id/public-queries only when the user asked for browser-safe reports or dashboards."},{"id":"funnel_3_step","kind":"query_blueprint","title":"3-step funnel with drop-offs","description":"Counts users who fired step1, then step2 (after step1), then step3 (after step2). Drop-off % between each step is computed by the agent from the returned counts.","category":"funnels","backend":"analytics","publication_safety":{"status":"safe_aggregate","public_access":"allowed","output_granularity":"aggregate","identifier_columns":[],"accepts_arbitrary_segment_property":false,"reason":"Returns aggregate analytics only; it exposes no person, account, or session identifiers and cannot select an arbitrary segment property."},"params":[{"name":"step1","type":"string","description":"First event (entry). Choose this from the saved signal map or observed event names.","required":true},{"name":"step2","type":"string","description":"Second event. Choose this from the saved signal map or observed event names.","required":true},{"name":"step3","type":"string","description":"Third event (conversion). Choose this from the saved signal map or observed event names.","required":true},{"name":"days","type":"number","description":"Lookback window in days","default":30}],"query":"WITH s1 AS (\n  SELECT distinct_id, min(timestamp) AS t1\n  FROM events\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND event = '{{step1}}' AND timestamp > now() - INTERVAL {{days}} DAY\n  GROUP BY distinct_id\n),\ns2 AS (\n  SELECT e.distinct_id, min(e.timestamp) AS t2\n  FROM events e\n  JOIN s1 ON s1.distinct_id = e.distinct_id\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND e.event = '{{step2}}' AND e.timestamp >= s1.t1\n  GROUP BY e.distinct_id\n),\ns3 AS (\n  SELECT e.distinct_id, min(e.timestamp) AS t3\n  FROM events e\n  JOIN s2 ON s2.distinct_id = e.distinct_id\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND e.event = '{{step3}}' AND e.timestamp >= s2.t2\n  GROUP BY e.distinct_id\n)\nSELECT\n  (SELECT count() FROM s1) AS step1_count,\n  (SELECT count() FROM s2) AS step2_count,\n  (SELECT count() FROM s3) AS step3_count","expected_columns":["step1_count","step2_count","step3_count"],"render_hint":{"type":"funnel","stages":["step1_count","step2_count","step3_count"],"title":"3-step funnel","notes":["Compute drop-off % from the returned counts: drop_n = 1 - (step_n+1 / step_n).","Render as a 3-row table (step name, count, drop-off %) or an ASCII funnel."]},"example_user_question":"what's the drop-off in our signup funnel?","endpoints":{"run":"/v1/projects/:project_id/query-blueprints/funnel_3_step/run","publish":"/v1/projects/:project_id/public-queries"},"publishable":true,"next_action":"Before running this analytics template for a product answer, compare its params with the saved signal map, current verified onboarding receipt, event names, and property keys. Query blueprints are executable templates, not answer-readiness proof. Blueprint defaults are examples, not proof of the customer's funnel; pass approved events/properties as params. If parameterized_required_event_properties is present, resolve the named params first and verify that exact event/property pair with event-property-keys. Publish with POST /v1/projects/:project_id/public-queries only when the user asked for browser-safe reports or dashboards."},{"id":"ordered_funnel_period_compare","kind":"query_blueprint","title":"Exact current-versus-prior ordered funnel","description":"Compares a 2–20 step ordered funnel across two adjacent exact half-open periods. scheduled_for is the exclusive end of the current period and must come from the trusted schedule receipt in unattended runs; direct owner calls must pass an explicit historical anchor.","category":"funnels","backend":"analytics","publication_safety":{"status":"private_automation","public_access":"denied","output_granularity":"aggregate","identifier_columns":[],"accepts_arbitrary_segment_property":false,"reason":"This exact-period aggregate is a run-bound automation source. Its scheduled_for anchor must come from the trusted trigger receipt; it is not a free-running public dashboard query."},"params":[{"name":"scheduled_for","type":"string","format":"rfc3339","description":"Trusted trigger-receipt timestamp and exclusive current-window end. Direct owner execution may supply an explicit RFC3339 timestamp with Z or a numeric offset; unattended runners must not invent or move it.","required":true},{"name":"period_days","type":"number","integer":true,"minimum":1,"maximum":90,"description":"Exact length of each adjacent comparison period in 24-hour days (1..90).","required":true},{"name":"funnel_events_json","type":"string","format":"event_name_list_json","description":"Canonical JSON array of 2–20 distinct approved events in funnel order.","required":true},{"name":"minimum_sample_size","type":"number","integer":true,"minimum":1,"maximum":100000,"description":"Minimum distinct step1 users required independently in both current and prior rows before sample_ready can be true.","required":true}],"query":"/* generated server-side by ordered_funnel_period_compare after strict param validation */","expected_columns":["period","window_start","window_end","funnel_events_json","observed_users_json","counts_json","conversion_percentages_json","overall_conversion_pct","sample_size","minimum_sample_size","all_selected_events_observed","sample_ready","readiness_status"],"render_hint":{"type":"table","title":"Current versus prior funnel","notes":["The two rows are exact adjacent half-open periods: prior then current. window_start is inclusive and window_end is exclusive.","The JSON arrays preserve the exact ordered event list, raw observed-user counts, ordered funnel counts, and step-to-step conversion percentages.","Do not publish conversion recommendations unless both rows have sample_ready=true. A false value is a readiness result, not permission to move scheduled_for or widen the period silently.","Marked onboarding-proof, rehearsal, and test analytics are excluded by the server-owned customer-data filter."]},"example_user_question":"How did our approved signup funnel perform this week versus the prior week?","example_params":{"scheduled_for":"2026-07-13T00:00:00.000Z","period_days":7,"funnel_events_json":"[\"signup_started\",\"signup_completed\",\"activation_completed\",\"first_value_reached\"]","minimum_sample_size":20},"strict_params":true,"query_builder":"ordered_funnel_period_compare","required_event_list_param":"funnel_events_json","automation_readiness":{"kind":"all_rows_boolean","column":"sample_ready","expected_rows":2,"row_identity":{"column":"period","values":["prior","current"]}},"endpoints":{"run":"/v1/projects/:project_id/query-blueprints/ordered_funnel_period_compare/run"},"publishable":false,"next_action":"For unattended execution, source scheduled_for only from the server-issued trigger receipt, preserve the exact declared params, and require automation_readiness.expected_rows with the exact row_identity order and every automation_readiness.column value true. Direct owner execution may pass an explicit historical scheduled_for. Never move the anchor or widen the period to manufacture readiness; false sample_ready rows are a truthful wait/telemetry result. This automation source is not public-query publishable."},{"id":"funnel_3_step_by_property","kind":"query_blueprint","title":"3-step funnel segmented by property","description":"Counts users through a 3-step funnel, grouped by a property from the entry event. Use this for drop-off by mobile/desktop, platform, browser, source, plan, account type, or any property captured on step1.","category":"funnels","backend":"analytics","publication_safety":{"status":"private_dynamic_segment","public_access":"denied","output_granularity":"aggregate","identifier_columns":[],"accepts_arbitrary_segment_property":true,"reason":"The caller chooses an arbitrary event property whose raw values become result rows; those values can contain identifiers or sensitive customer data."},"params":[{"name":"step1","type":"string","description":"First event (entry). Choose this from the saved signal map or observed event names.","required":true},{"name":"step2","type":"string","description":"Second event. Choose this from the saved signal map or observed event names.","required":true},{"name":"step3","type":"string","description":"Third event (conversion). Choose this from the saved signal map or observed event names.","required":true},{"name":"segment_property","type":"string","description":"Property to group by from step1. Check event-property-keys for that exact event first; do not assume an SDK ambient property exists. Prefer a source-verified business property, or an observed PostHog property such as $device_type, $os, or $browser when its meaning fits the question.","required":true},{"name":"days","type":"number","description":"Lookback window in days","default":30}],"query":"WITH s1 AS (\n  SELECT\n    distinct_id,\n    min(timestamp) AS t1,\n    argMin(\n      ifNull(nullIf(toString(properties['{{segment_property}}']), ''), 'unknown'),\n      timestamp\n    ) AS segment\n  FROM events\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND event = '{{step1}}' AND timestamp > now() - INTERVAL {{days}} DAY\n  GROUP BY distinct_id\n),\ns2 AS (\n  SELECT e.distinct_id, min(e.timestamp) AS t2\n  FROM events e\n  JOIN s1 ON s1.distinct_id = e.distinct_id\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND e.event = '{{step2}}' AND e.timestamp >= s1.t1\n  GROUP BY e.distinct_id\n),\ns3 AS (\n  SELECT e.distinct_id, min(e.timestamp) AS t3\n  FROM events e\n  JOIN s2 ON s2.distinct_id = e.distinct_id\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND e.event = '{{step3}}' AND e.timestamp >= s2.t2\n  GROUP BY e.distinct_id\n)\nSELECT\n  s1.segment AS segment,\n  count() AS step1_count,\n  countIf(s2.distinct_id IS NOT NULL) AS step2_count,\n  countIf(s3.distinct_id IS NOT NULL) AS step3_count\nFROM s1\nLEFT JOIN s2 ON s2.distinct_id = s1.distinct_id\nLEFT JOIN s3 ON s3.distinct_id = s1.distinct_id\nGROUP BY segment\nORDER BY step1_count DESC\nLIMIT 50","expected_columns":["segment","step1_count","step2_count","step3_count"],"render_hint":{"type":"funnel","stages":["step1_count","step2_count","step3_count"],"title":"3-step funnel by segment","notes":["Compute drop-off % within each segment from the returned counts.","If segment is unknown, the chosen property is missing on step1; report that as a telemetry gap before over-interpreting the split."]},"example_user_question":"where does our signup funnel drop off by device type?","example_params":{"step1":"page_view","step2":"signup_started","step3":"signup_completed","segment_property":"$device_type","days":30},"recommended_segment_properties":["$device_type","$os","$browser","$is_mobile","$platform","$pathname","source","plan"],"parameterized_required_event_properties":[{"event_param":"step1","property_param":"segment_property","check_endpoint":"/v1/projects/:project_id/event-property-keys?events={{step1}}","description":"Before running or rendering this segmented funnel, resolve step1 and segment_property from params and confirm the selected property is observed on the selected entry event."}],"endpoints":{"run":"/v1/projects/:project_id/query-blueprints/funnel_3_step_by_property/run"},"publishable":false,"next_action":"Run this template only through the owner-authenticated query-blueprint endpoint. Compare its params with the saved signal map, current verified onboarding receipt, event names, and property keys first. If parameterized_required_event_properties is present, resolve and verify the exact event/property pair before running it. publication_safety explains why it cannot be made public: identifier-bearing rows and caller-selected segment values stay private even when the backend is analytics. Choose a safe_aggregate blueprint for browser or shareable output."},{"id":"funnel_dropoff_users","kind":"query_blueprint","title":"Users who entered a funnel but did not complete","description":"Returns bounded distinct_id rows for users who fired an entry event in the lookback window but did not later fire the completion event. If the customer records sessions, use the ids only to locate candidate recordings directly in PostHog; Agentry never proxies replay snapshots.","category":"funnels","backend":"analytics","publication_safety":{"status":"private_identifiers","public_access":"denied","output_granularity":"row_level","identifier_columns":["distinct_id","session_id","session_recording_id"],"accepts_arbitrary_segment_property":false,"reason":"Returns row-level person and session identifiers for owner-side investigation."},"params":[{"name":"entry_event","type":"string","description":"Funnel entry event. Choose this from the saved signal map or observed event names.","required":true},{"name":"completion_event","type":"string","description":"Funnel completion event. Choose this from the saved signal map or observed event names.","required":true},{"name":"days","type":"number","description":"Lookback window in days","default":7},{"name":"limit","type":"number","description":"Max drop-off users to return for replay review","default":25}],"query":"WITH entry AS (\n  SELECT\n    distinct_id,\n    min(timestamp) AS entry_at,\n    argMin(ifNull(toString(properties['$session_id']), ''), timestamp) AS session_id,\n    argMin(ifNull(toString(properties['$session_recording_id']), ''), timestamp) AS session_recording_id\n  FROM events\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND event = '{{entry_event}}'\n    AND timestamp > now() - INTERVAL {{days}} DAY\n  GROUP BY distinct_id\n),\ncompletion AS (\n  SELECT e.distinct_id, min(e.timestamp) AS completed_at\n  FROM events e\n  JOIN entry ON entry.distinct_id = e.distinct_id\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND e.event = '{{completion_event}}'\n    AND e.timestamp >= entry.entry_at\n  GROUP BY e.distinct_id\n),\nactivity AS (\n  SELECT e.distinct_id, max(e.timestamp) AS last_seen_at\n  FROM events e\n  JOIN entry ON entry.distinct_id = e.distinct_id\n  WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n    AND e.timestamp >= entry.entry_at\n  GROUP BY e.distinct_id\n)\nSELECT\n  entry.distinct_id AS distinct_id,\n  entry.entry_at AS entry_at,\n  activity.last_seen_at AS last_seen_at,\n  dateDiff('minute', entry.entry_at, activity.last_seen_at) AS minutes_observed,\n  entry.session_id AS session_id,\n  entry.session_recording_id AS session_recording_id\nFROM entry\nLEFT JOIN completion ON completion.distinct_id = entry.distinct_id\nLEFT JOIN activity ON activity.distinct_id = entry.distinct_id\nWHERE completion.distinct_id IS NULL\nORDER BY entry.entry_at DESC\nLIMIT {{limit}}","expected_columns":["distinct_id","entry_at","last_seen_at","minutes_observed","session_id","session_recording_id"],"render_hint":{"type":"table","title":"Funnel drop-off users","notes":["Use these private identifier rows as investigation candidates, not as a final statistical summary.","If session_recording_id is present, an owner may locate that recording directly in PostHog. Agentry does not fetch or store replay snapshots.","If no recording id is present, report the missing evidence rather than inventing a replay lookup through Agentry."]},"example_user_question":"which incomplete onboarding sessions should I consider reviewing directly in PostHog?","example_params":{"entry_event":"onboarding_started","completion_event":"onboarding_completed","days":7,"limit":25},"endpoints":{"run":"/v1/projects/:project_id/query-blueprints/funnel_dropoff_users/run"},"publishable":false,"next_action":"Run this template only through the owner-authenticated query-blueprint endpoint. Compare its params with the saved signal map, current verified onboarding receipt, event names, and property keys first. If parameterized_required_event_properties is present, resolve and verify the exact event/property pair before running it. publication_safety explains why it cannot be made public: identifier-bearing rows and caller-selected segment values stay private even when the backend is analytics. Choose a safe_aggregate blueprint for browser or shareable output."},{"id":"top_events_30d","kind":"query_blueprint","title":"Top events by frequency (last N days)","description":"Most-fired event names — useful for understanding what your users actually do.","category":"events","backend":"analytics","publication_safety":{"status":"safe_aggregate","public_access":"allowed","output_granularity":"aggregate","identifier_columns":[],"accepts_arbitrary_segment_property":false,"reason":"Returns aggregate analytics only; it exposes no person, account, or session identifiers and cannot select an arbitrary segment property."},"params":[{"name":"days","type":"number","description":"Lookback in days","default":30},{"name":"limit","type":"number","description":"Max rows","default":20}],"query":"SELECT event, count() AS occurrences\nFROM events\nWHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['agentry_onboarding_proof']), '')) = '' AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')\n  AND timestamp > now() - INTERVAL {{days}} DAY\nGROUP BY event\nORDER BY occurrences DESC\nLIMIT {{limit}}","expected_columns":["event","occurrences"],"render_hint":{"type":"bar","x":"event","y":"occurrences","title":"Top events by frequency"},"example_user_question":"what events do my users fire most?","endpoints":{"run":"/v1/projects/:project_id/query-blueprints/top_events_30d/run","publish":"/v1/projects/:project_id/public-queries"},"publishable":true,"next_action":"Before running this analytics template for a product answer, compare its params with the saved signal map, current verified onboarding receipt, event names, and property keys. Query blueprints are executable templates, not answer-readiness proof. Blueprint defaults are examples, not proof of the customer's funnel; pass approved events/properties as params. If parameterized_required_event_properties is present, resolve the named params first and verify that exact event/property pair with event-property-keys. Publish with POST /v1/projects/:project_id/public-queries only when the user asked for browser-safe reports or dashboards."}]},"automation":{"guide_url":"/v1/docs/automation","flow_schema_url":"/v1/openapi.json?flow=automation","playbook_catalog_url":"/v1/automation-playbooks","control_url":"/v1/projects/{project_id}/automation-control","versions":{"automation_contract":"2"},"immutable_templates":["error-to-draft-pr@4.0.0","weekly-funnel-review@2.0.0"],"note":"Use the flow's stable operation IDs and exact principals/headers. Playbook rendering is side-effect free; enabling an automation is the only authority-granting step."},"onboarding":{"bootstrap_url":"https://raw.githubusercontent.com/agentry-sh/agentry/main/INSTALL.md","flow_schema_url":"/v1/openapi.json?flow=onboarding","state_url":"/v1/projects/{project_id}/onboarding","source_snapshot_helper_url":"/v1/onboarding/source-snapshot.mjs","note":"Five project operations move through planned, approved, proving, and verified. Follow state.next_action after every operation. Completion is only status=verified, installation_complete=true, and next_action=null after real proof; caller-authored or synthetic proof never counts."},"posthog_resources":{"flow_schema_url":"/v1/openapi.json?flow=posthog","resource_families":["session_recordings","feature_flags","surveys","experiments"],"note":"Canonical GET-only projections of live customer PostHog state. Responses are bounded and carry freshness plus data_boundary; replay content and provider mutations remain outside Agentry."},"docs":{"query_docs":"/v1/docs/query","automation_docs":"/v1/docs/automation","privacy_disclosure":"/v1/privacy/disclosure"}},"capabilities":[{"id":"mutation_recovery","kind":"cross_cutting_contract","source":"MUTATION_ROUTE_POLICIES joined to exact filtered OpenAPI operations","auth":["api_key","ci_deploy","automation_runner"],"endpoint_count":24,"endpoints":["DELETE /v1/projects/{id}","DELETE /v1/projects/{project_id}/public-queries/{publication_id}","PATCH /v1/cases/{case_id}","POST /v1/auth/keys/{key_id}/revoke","POST /v1/auth/keys/rotate","POST /v1/auth/self_delete","POST /v1/automation-runs/{run_id}/actions/auto-merge/authorize","POST /v1/automation-runs/{run_id}/actions/draft-pr/authorize","POST /v1/automation-runs/{run_id}/actions/record-report","POST /v1/automation-runs/{run_id}/cancel","POST /v1/automation-runs/{run_id}/report-source","POST /v1/billing/checkout","POST /v1/billing/portal","POST /v1/cases/{case_id}/claims","POST /v1/deploys/","POST /v1/feedback","POST /v1/project-credentials/{credential_id}/revoke","POST /v1/projects","POST /v1/projects/{project_id}/alerts","POST /v1/projects/{project_id}/automation-runs","POST /v1/projects/{project_id}/credentials","POST /v1/projects/{project_id}/public-queries","POST /v1/projects/{project_id}/webhooks","POST /v1/projects/{project_id}/webhooks/{id}/test"],"operations":[{"method":"DELETE","path":"/v1/projects/{id}","operation":"DELETE /v1/projects/{id}","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"none","max_bytes":0},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bid%7D&method=delete"},{"method":"DELETE","path":"/v1/projects/{project_id}/public-queries/{publication_id}","operation":"DELETE /v1/projects/{project_id}/public-queries/{publication_id}","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"none","max_bytes":0},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fpublic-queries%2F%7Bpublication_id%7D&method=delete"},{"method":"PATCH","path":"/v1/cases/{case_id}","operation":"PATCH /v1/cases/{case_id}","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fcases%2F%7Bcase_id%7D&method=patch"},{"method":"POST","path":"/v1/auth/keys/{key_id}/revoke","operation":"POST /v1/auth/keys/{key_id}/revoke","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":8192},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fauth%2Fkeys%2F%7Bkey_id%7D%2Frevoke&method=post"},{"method":"POST","path":"/v1/auth/keys/rotate","operation":"POST /v1/auth/keys/rotate","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"none","max_bytes":0},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fauth%2Fkeys%2Frotate&method=post"},{"method":"POST","path":"/v1/auth/self_delete","operation":"POST /v1/auth/self_delete","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":4096},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fauth%2Fself_delete&method=post"},{"method":"POST","path":"/v1/automation-runs/{run_id}/actions/auto-merge/authorize","operation":"POST /v1/automation-runs/{run_id}/actions/auto-merge/authorize","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fautomation-runs%2F%7Brun_id%7D%2Factions%2Fauto-merge%2Fauthorize&method=post"},{"method":"POST","path":"/v1/automation-runs/{run_id}/actions/draft-pr/authorize","operation":"POST /v1/automation-runs/{run_id}/actions/draft-pr/authorize","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fautomation-runs%2F%7Brun_id%7D%2Factions%2Fdraft-pr%2Fauthorize&method=post"},{"method":"POST","path":"/v1/automation-runs/{run_id}/actions/record-report","operation":"POST /v1/automation-runs/{run_id}/actions/record-report","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fautomation-runs%2F%7Brun_id%7D%2Factions%2Frecord-report&method=post"},{"method":"POST","path":"/v1/automation-runs/{run_id}/cancel","operation":"POST /v1/automation-runs/{run_id}/cancel","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fautomation-runs%2F%7Brun_id%7D%2Fcancel&method=post"},{"method":"POST","path":"/v1/automation-runs/{run_id}/report-source","operation":"POST /v1/automation-runs/{run_id}/report-source","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fautomation-runs%2F%7Brun_id%7D%2Freport-source&method=post"},{"method":"POST","path":"/v1/billing/checkout","operation":"POST /v1/billing/checkout","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":16384,"optional_empty":true},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fbilling%2Fcheckout&method=post"},{"method":"POST","path":"/v1/billing/portal","operation":"POST /v1/billing/portal","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":16384,"optional_empty":true},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fbilling%2Fportal&method=post"},{"method":"POST","path":"/v1/cases/{case_id}/claims","operation":"POST /v1/cases/{case_id}/claims","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fcases%2F%7Bcase_id%7D%2Fclaims&method=post"},{"method":"POST","path":"/v1/deploys/","operation":"POST /v1/deploys/","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fdeploys%2F&method=post"},{"method":"POST","path":"/v1/feedback","operation":"POST /v1/feedback","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":16384},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Ffeedback&method=post"},{"method":"POST","path":"/v1/project-credentials/{credential_id}/revoke","operation":"POST /v1/project-credentials/{credential_id}/revoke","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":8192},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fproject-credentials%2F%7Bcredential_id%7D%2Frevoke&method=post"},{"method":"POST","path":"/v1/projects","operation":"POST /v1/projects","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":16384},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects&method=post"},{"method":"POST","path":"/v1/projects/{project_id}/alerts","operation":"POST /v1/projects/{project_id}/alerts","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":16384},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Falerts&method=post"},{"method":"POST","path":"/v1/projects/{project_id}/automation-runs","operation":"POST /v1/projects/{project_id}/automation-runs","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":262144},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fautomation-runs&method=post"},{"method":"POST","path":"/v1/projects/{project_id}/credentials","operation":"POST /v1/projects/{project_id}/credentials","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":16384},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fcredentials&method=post"},{"method":"POST","path":"/v1/projects/{project_id}/public-queries","operation":"POST /v1/projects/{project_id}/public-queries","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":65536},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fpublic-queries&method=post"},{"method":"POST","path":"/v1/projects/{project_id}/webhooks","operation":"POST /v1/projects/{project_id}/webhooks","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"json","max_bytes":16384},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fwebhooks&method=post"},{"method":"POST","path":"/v1/projects/{project_id}/webhooks/{id}/test","operation":"POST /v1/projects/{project_id}/webhooks/{id}/test","retry_kind":"idempotency_key","idempotency_key_required":true,"request_body":{"kind":"none","max_bytes":0},"rationale":"A durable command binds actor, scope, action, key, and canonical evidence; exact replay returns the original receipt.","openapi_filter":"/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Fwebhooks%2F%7Bid%7D%2Ftest&method=post"}],"invariants":["Every generated operation requires one stable 8-500 character Idempotency-Key. Its exact replay or reconciliation behavior comes from retry_kind and the filtered OpenAPI operation, never a prose-only subset.","Webhook signing secrets use encrypted exact-replay recovery; owner and project-scoped private credentials remain hash-only.","Owner-key rotation is staged: the previous key remains active until a verified replacement explicitly revokes it.","A lost project-credential response never mints another active credential; the retry names the existing unrecoverable credential for explicit cleanup.","Webhook test delivery identity comes from the stable command, never the transport request id."]},{"id":"onboarding","kind":"flow","auth":["none","api_key","ci_deploy"],"endpoints":["/v1/setup/manifest","/v1/onboarding/source-snapshot.mjs","/v1/projects/{project_id}/onboarding","/v1/projects/{project_id}/onboarding/review","/v1/projects/{project_id}/onboarding/proof","/v1/projects/{project_id}/onboarding/verify","/v1/projects/{project_id}/signal-map","/v1/projects/{project_id}/setup"],"readiness_contract":["Discovery submits one complete source-grounded value-flow plan with no fixed event count or naming convention.","A human approves the exact plan, then live proof checks its events, properties, safe failure, and deploy before status=verified.","Every nonterminal state names one next_action; only verified returns installation_complete=true and next_action=null."]},{"id":"error_monitoring","kind":"feature","auth":["api_key"],"endpoints":["/v1/projects/{project_id}/cases","/v1/cases/{case_id}"],"combines_with":["deploy_attribution","posthog_source_maps","affected_users"],"authority":"PostHog owns direct exception capture, grouping, issue lifecycle, affected-user impact, and symbolication. Agentry reads bounded issue evidence through the customer's OAuth connection and keeps only its thin agent-control projection."},{"id":"product_analytics","kind":"feature","auth":["api_key"],"endpoints":["/v1/projects/{project_id}/analytics/query","/v1/projects/{project_id}/event-names","/v1/projects/{project_id}/event-property-keys"],"combines_with":["verified_onboarding","query_blueprints","public_queries","alerts"]},{"id":"posthog_resources","kind":"read_only_provider_resources","auth":["api_key"],"discovery":{"schema":"/v1/openapi.json?flow=posthog","tags":["Session Recordings","Feature Flags","Surveys","Experiments"]},"resources":["session_recordings","feature_flags","surveys","survey_responses","survey_stats","experiments"],"endpoints":["/v1/projects/{project_id}/session-recordings","/v1/projects/{project_id}/session-recordings/{recording_id}","/v1/projects/{project_id}/feature-flags","/v1/projects/{project_id}/feature-flags/{flag_id}","/v1/projects/{project_id}/surveys","/v1/projects/{project_id}/surveys/{survey_id}","/v1/projects/{project_id}/surveys/{survey_id}/responses","/v1/projects/{project_id}/surveys/{survey_id}/stats","/v1/projects/{project_id}/experiments","/v1/projects/{project_id}/experiments/{experiment_id}"],"authority":"The connected customer PostHog Cloud project remains authoritative. Agentry reads through read-only OAuth, returns a bounded typed projection, and does not retain a provider resource snapshot or raw event stream.","invariants":["Session-recording reads expose metadata and player_url only; replay snapshots, DOM content, and video never cross Agentry.","Survey response reads require a caller-supplied window no longer than 90 days and carry explicit redaction metadata.","Experiment reads expose definitions and provider conclusion state, not an experiment-results endpoint or a causal-effect claim.","No endpoint creates, updates, archives, targets, concludes, deletes, or otherwise mutates PostHog resources."]},{"id":"query_blueprints","kind":"query_templates","auth":["none","api_key"],"endpoints":["/v1/query-blueprints","/v1/query-blueprints/{blueprint_id}","/v1/projects/{project_id}/query-blueprints/{blueprint_id}/run"],"count":21,"categories":["deploys","errors","events","funnels","retention","users"]},{"id":"automation_v2","kind":"flow","auth":["api_key","ci_deploy","automation_runner"],"discovery":{"guide":"/v1/docs/automation","schema":"/v1/openapi.json?flow=automation","templates":"/v1/automation-playbooks"},"endpoints":["/v1/automation-playbooks","/v1/projects/{project_id}/automation-playbooks/{id}/render","/v1/projects/{project_id}/automation-contract","/v1/projects/{project_id}/automation-control","/v1/projects/{project_id}/automation-triggers/issue","/v1/projects/{project_id}/automation-trigger-receipts","/v1/automation-trigger-receipts/{receipt_id}","/v1/cases/{case_id}/claims","/v1/cases/{case_id}","/v1/projects/{project_id}/automation-runs","/v1/automation-runs/{run_id}","/v1/automation-runs/{run_id}/verify","/v1/automation-runs/{run_id}/report-source","/v1/automation-runs/{run_id}/actions/record-report","/v1/automation-runs/{run_id}/cancel"],"invariants":["Contracts save disabled and owners enable an exact ETag.","A trigger receipt is the runner's pre-claim evidence source. Detailed PostHog-backed case evidence is available only while the exact runner credential holds the current unexpired claim.","Telemetry and runner text have no instruction authority.","Draft PR authority never includes merge, deploy, rollback, or direct push.","Verification is server-owned; zero traffic never proves success.","Scheduled reports use one immutable aggregate source and remain private until a human chooses follow-up."]},{"id":"agent_ops","kind":"workflow","auth":["api_key"],"endpoints":["/v1/projects/{project_id}/next-steps","/v1/projects/{project_id}/health","/v1/audit/recent"],"combines_with":["webhooks","alerts"]},{"id":"billing","kind":"management","auth":["api_key","stripe_webhook"],"endpoints":["/v1/billing/status","/v1/billing/checkout","/v1/billing/portal","/v1/billing/webhook"],"combines_with":["automation"],"agent_behavior":"Billing applies only to the hosted Nerve control plane. Customer PostHog events and customer-run inference are never metered. Billing reads are side-effect free; create a Stripe Checkout link only after an owner chooses Nerve and calls POST /v1/billing/checkout with a stable Idempotency-Key."}],"next_action":"Choose the matching flow. For current recording, flag, survey, or experiment state, follow /v1/openapi.json?flow=posthog. For behavioral or effect questions, prove readiness from the signal map, its source_verified receipt, and bounded customer PostHog event/property reads before query blueprints or custom HogQL. For automations, follow /v1/docs/automation and /v1/openapi.json?flow=automation with exact principals and headers."}