> **creating-ai-subscription** — skill 33 of 200 in [PostHog/skills](https://skillsdocs.com/PostHog/skills).
>
> Book (all skills, one file): https://skillsdocs.com/PostHog/skills.md
> Machine manifest: https://skillsdocs.com/PostHog/skills/.well-known/agent-skills/index.json
> Install the book: `npx skills add PostHog/skills`
> Upstream: https://github.com/PostHog/skills/blob/main/skills/omnibus/creating-ai-subscription/SKILL.md @ `main`
> Raw bytes, no header: https://raw.githubusercontent.com/PostHog/skills/main/skills/omnibus/creating-ai-subscription/SKILL.md
> Base for relative paths: https://raw.githubusercontent.com/PostHog/skills/main/skills/omnibus/creating-ai-subscription/
> Licence: MIT — https://spdx.org/licenses/MIT.html
>
> Content © its authors, served unmodified. Takedown: https://github.com/DreambaseAI/skillsdocs/issues/new?labels=takedown&title=Takedown+request

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: creating-ai-subscription
description: >
  Create a recurring AI-generated PostHog report — schedule a free-text prompt to
  run on a cron, with the LLM-synthesized markdown delivered to email, Slack, or Microsoft Teams
  on each tick. Use when the user wants a recurring AI summary of X on any cadence
  (daily, weekly, monthly, yearly) rather than a one-off report. (To attach an AI
  summary to an existing insight/dashboard
  subscription instead of a free-text prompt, see `managing-subscriptions` and its
  `summary_enabled` option.)
---

# Creating a prompt subscription

## When to use this

A **subscription** delivers a PostHog report to email, Slack, or Microsoft Teams on a
recurring schedule. There are three kinds, distinguished by which field you set — the kind is
derived and returned as the read-only `resource_type`:

- **`insight`** — periodic snapshots of one existing insight (`resource_type: "insight"`)
- **`dashboard`** — periodic snapshots of a dashboard's tiles (`resource_type: "dashboard"`)
- **`prompt`** — a recurring **AI-generated** report from a free-text prompt: an LLM
  plans and runs HogQL over the project's data and synthesizes a fresh markdown report
  each tick (`resource_type: "ai_prompt"`)

Use **this** skill for the **prompt** kind — i.e. when the user wants a recurring AI
summary of X (on any cadence — daily, weekly, monthly, yearly) rather than a recurring
snapshot of one existing insight/dashboard, or a single one-off report. Pick a prompt subscription when the
value is the _analysis itself_ (the LLM deciding what to query and writing it up),
not a fixed chart they already built. For an insight/dashboard subscription, set
`insight`/`dashboard` instead of `prompt` and the AI gates below don't apply.

> **Prefer a dashboard or insight subscription first.** A prompt subscription is the
> heaviest option, and the LLM composes its own HogQL, so its numbers can drift from
> what a saved insight or dashboard already shows. Reach for it only when (a) the user
> **specifically asks** for a free-text / AI-written report, or (b) no existing insight
> or dashboard covers the ask and the value really is the analysis itself. If the user
> wants the key numbers from an **existing dashboard or insight** delivered on a
> schedule — even phrased as "set up a scout/bot to post this daily" — a
> **dashboard (or insight) subscription with `summary_enabled: true`** is usually the
> better fit. Respect a user who's sure they want a prompt subscription, but when it's
> ambiguous, suggest that and confirm first. See `managing-subscriptions` for the happy
> path.

This skill covers **creating** the subscription. Once it exists you manage its
lifecycle with the same `subscriptions-*` tools (see below): list it, edit/disable/
re-enable it, send a test delivery, or delete it.

## Tools

| Tool                                         | Purpose                                             |
| -------------------------------------------- | --------------------------------------------------- |
| `posthog:subscriptions-create`               | Create the recurring prompt subscription            |
| `posthog:subscriptions-list`                 | Confirm it landed; inspect existing subscriptions   |
| `posthog:subscriptions-partial-update`       | Edit, disable (`enabled: false`), or re-enable it   |
| `posthog:subscriptions-test-delivery-create` | Send an immediate test delivery to its target(s)    |
| `posthog:subscriptions-delete`               | Soft-delete it (stops all future deliveries)        |
| `posthog:integrations-list`                  | Find a Slack `integration_id` (filter `kind=slack`) |
| `posthog:integrations-channels-retrieve`     | List a Slack integration's channels (id + name)     |

## What you need before calling

The endpoint enforces three create-time gates and will return 400 if any fails:

1. **PostHog Cloud, or `DEBUG=true`** — self-hosted production deployments are not
   eligible (the LLM call routes through a PostHog-managed key).
2. **Org-level "AI data processing approved"** — must be toggled on in
   `Org settings → Data → AI data processing`. The user must opt in to AI features
   for the organization first.
3. **Prompt subscriptions enabled** for the organization — a PostHog-managed rollout
   flag. If it's off, the org has not been granted access yet; tell the user to
   reach out to PostHog to enable it (there is no self-serve toggle).

If any of the three is missing, stop and tell the user which one to fix —
re-calling the tool will not help.

Your access token also needs the **`query:read`** scope in addition to
`subscription:write`: a prompt subscription runs LLM-generated HogQL over the project's
data, so the backend requires query access to create, edit/re-enable, test-deliver,
or delete one. A `subscription:write`-only token is rejected with a 403.

## Required arguments

```yaml
prompt: "..."                         # ≤4000 chars; setting this (with no insight/dashboard) makes it a prompt sub → resource_type "ai_prompt"
target_type: "email" | "slack" | "teams"  # the only delivery channels the API accepts
target_value: "..."                   # comma-separated emails, "<channel_id>|<channel_name>", or a Microsoft Teams webhook URL
frequency: "daily" | "weekly" | "monthly" | "yearly"
interval: 1                            # 1 = every tick; 2 = every other tick; etc.
start_date: "2026-09-15T09:30:00Z"   # anchors the recurrence + time-of-day; hour and half-hour slots are supported; need not be in the future
title: "..."                          # display name in the subscriptions list
send_test_now: false                   # set true only after the user approves an immediate delivery
```

There is no `resource_type` argument to send — the kind is **derived**
from which field you set (`prompt` ⇒ AI report) and returned as the read-only `resource_type`.

## Optional arguments

```yaml
byweekday: ['monday', 'wednesday'] # weekly only — days the rrule fires
bysetpos: 1 # most useful with monthly; requires byweekday — e.g. byweekday:['monday']+bysetpos:-1 = last Monday
count: 10 # limit scheduled recurrence occurrences; manual and immediate deliveries do not count
until_date: '2026-12-31T00:00:00Z' # stop on/before this date
integration_id: 42 # Slack only — required; from integrations-list (see "Slack target")
```

## Slack target

`target_value` must be `<channel_id>|<channel_name>` (the format the integration
returns). Build it in three steps:

1. `posthog:integrations-list` filtered by `kind=slack` → pick the Slack
   integration's `id`.
2. `posthog:integrations-channels-retrieve` with that `id` → pick a channel; it
   returns each channel's `id` and `name`, which you assemble into `target_value`
   as `<id>|<name>`.
3. Pass that integration's `id` as `integration_id` — the subscription is pinned
   to one specific Slack integration so reconnections elsewhere don't accidentally
   re-route deliveries.

## Teams target

`target_value` must be the full webhook URL of the channel you want reports in. The user adds the
Workflows app to that channel, picks the template for posting to a channel when a webhook request
is received, then pastes the URL back to you. A URL from a different template saves without an
error, but the report never reaches the channel. There is no Teams integration to look up, so
leave `integration_id` out.

The URL must be `https`. If `subscriptions-create` rejects `target_value`, the URL is not a Teams
webhook — ask the user to create it again with the same template and paste the whole URL. On a
later update, omit `target_value` to keep the saved URL, because the API reads it back as its host
only and rejects that masked value.

## Before creation

List matching subscriptions before you create one.
Compare the prompt, title, destination, schedule, and enabled state.
Prompt text and titles both match `search`.

Creation is not idempotent.
If the create request times out or returns an uncertain result, list matching subscriptions before you retry.
Do not use a masked Teams webhook host to identify a duplicate.

Ask whether the user approves an immediate delivery.
Set `send_test_now: false` unless the user approves it.

## Examples

### Weekly Monday-morning AI summary by email

```yaml
prompt: 'Top events week over week, with the biggest drops and any new failure modes called out.'
target_type: email
target_value: founders@acme.example
frequency: weekly
interval: 1
byweekday: ['monday']
start_date: '2026-09-14T08:00:00Z'
title: 'Weekly product pulse'
send_test_now: false
```

### Daily Slack report at 9am

```yaml
prompt: "Yesterday's sign-ups, where they came from, and any errors they hit during onboarding."
target_type: slack
target_value: 'C0123456789|growth-updates' # <channel_id>|<channel_name>; only the channel id is used, the name is cosmetic
integration_id: 42
frequency: daily
interval: 1
start_date: '2026-09-15T09:00:00Z'
title: 'Daily onboarding watch'
send_test_now: false
```

## Pitfalls

- **A Teams webhook URL is a credential.** Anyone who has it can post in the channel, which is
  why the API only ever returns its host. Pass it only as `target_value`. Do not echo it back to
  the user, and do not copy it into a confirmation message, a summary, or an error report.
- **The kind is immutable.** It's derived from which relation is set, so you can't flip an
  insight or dashboard sub into a prompt sub after the fact (or vice versa) — a PATCH that adds a
  `prompt` to an insight sub is rejected. Pick the right kind at create time.
- **Re-enabling a previously auto-disabled prompt sub** has two preconditions, both
  enforced on the PATCH: (1) a valid `prompt` — already persisted on the row, or a
  new one in the PATCH body (so bare `{"enabled": true}` works when the stored prompt
  is still valid, but is rejected when the disable cause was an invalid prompt until
  you supply a good one); and (2) the **original creator is still an active user** —
  if that account was deactivated the sub cannot be re-enabled at all (no prompt will
  help; re-create it instead).
- **`next_delivery_date` is server-computed from the rrule.** Don't try to set it
  manually — it's read-only. The first delivery fires at the first `start_date`
  occurrence that is at least a short buffer (currently ~15 minutes) in the future,
  so a `start_date` only seconds ahead rolls to the next occurrence.
- **Transient send failures retry; only permanent failures auto-disable.** A
  transient failure (Slack rate limit, SMTP blip, network) fails that delivery and
  is retried by Temporal within the run, then re-fires on the next scheduled tick —
  it does **not** auto-disable the subscription, so a persistently-failing channel
  will keep retrying every tick until you fix it. Only permanent/structural causes
  auto-disable: a disconnected Slack integration, a revoked channel permission, an
  invalid prompt, or revoked AI data-processing consent. (For multi-recipient email,
  a delivery only fails when _every_ recipient fails; partial successes still send.)
  Within a single delivery run the rendered markdown is cached, so Temporal retries
  of that run don't re-run the LLM pipeline — but each new scheduled tick generates a
  fresh report.

## After it lands

`subscriptions-list` will return the new row. Confirm `resource_type: "ai_prompt"`,
`enabled: true`, `next_delivery_date` is in the future, and `prompt` matches what
you sent. The first scheduled tick will run the planner → HogQL → synthesis
pipeline and deliver the rendered markdown to the channel you chose.

## Related skills

- **`managing-subscriptions`** — insight and dashboard subscriptions, including AI summaries attached to them
- **`building-a-dashboard`** — build the dashboard when the user wants charts rather than a written report
