---
name: open-claw
description: Run AutoPod blogs and podcasts on full daily autopilot. Use when the user wants to operate one or many AutoPod blogs hands-free — propose 3 fresh article topics per blog each day, accept the user's number-only choices (or skips), kick off generation, then file a 2-hour follow-up review of what shipped or broke.
version: 1.0.0
---

# Open Claw — AutoPod Daily Autopilot

Open Claw is the operator skill for [AutoPod](https://autopod.co). It turns
Claude into your daily content manager: every day you get 3 suggested article
titles per blog, you reply with numbers (or "skip"), Claude triggers generation,
and 2 hours later it tells you what shipped — and what, if anything, didn't.

## When to use

Use this skill when the user:

- Wants their AutoPod blog(s) on full autopilot.
- Asks you to "run today's blogs", "do today's autopod", "what should I write
  today on \<blog name\>", or anything similar.
- Asks you to check on results of articles you triggered earlier today.
- Triggers you via a daily scheduled task that mentions Open Claw / AutoPod.

Do NOT use this skill for general writing assistance unrelated to AutoPod — it
only operates against the AutoPod REST API at `https://autopod.co/api/v1`.

## One-time setup the user has to do

Before you can run the daily flow, the user needs to give you an API key.
Store it in their environment as `AUTOPOD_API_KEY`:

- **Tenant key** (`apod_…`) — manages exactly one blog. Get it from
  Dashboard → API tab → "Tenant API Key" inside that blog.
- **Account key** (`apoa_…`) — manages every blog the user owns from one
  Claude. Recommended for owners with multiple blogs. Get it from
  Dashboard → API tab → "Account API Key (multi-blog)".

Both require an active subscription (Starter / Growth / Scale all work).

If the user hasn't provided a key yet, ask once for the key and the model
they prefer (account or tenant). Don't keep asking — just remind them on the
next turn if it's still missing.

## Recommended schedule (the user sets this up in Claude)

Open Claw is meant to be triggered by a Claude scheduled task — Claude itself
isn't always-on. Suggest these to the user during setup:

1. **Daily morning task** (e.g. 09:00 user-local): "Run today's Open Claw
   suggestions for AutoPod." → Claude executes the **Daily flow** below.
2. **Follow-up task scheduled by Claude** at "now + 2 hours" after the user
   confirms picks → Claude executes the **2-hour review flow**.

If your runtime supports scheduling new tasks programmatically, schedule the
2-hour review yourself the moment the user finishes picking. Otherwise, at
the end of the daily flow, instruct the user to set the 2-hour reminder.

---

## Daily flow

Run these steps in order. Stop and report immediately on the first hard error.

### 1. List blogs the key can manage

```bash
curl -sS -X POST https://autopod.co/api/v1 \
  -H "Authorization: Bearer $AUTOPOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"listBlogs"}'
```

Expected response:

```json
{
  "success": true,
  "principal": { "kind": "account" },
  "blogs": [
    {
      "id": "tenantA",
      "name": "Visual Field Test",
      "slug": "vft",
      "plan": "scale",
      "status": "active",
      "niche": "eye health",
      "articlesUsedThisMonth": 12,
      "articlesLimitThisMonth": 40,
      "articlesRemainingThisMonth": 28,
      "apiAccessEnabled": true
    }
  ]
}
```

If `success` is false or the call returns 401/403, stop and tell the user
exactly what the API said. Do not retry blindly.

Skip any blog where `apiAccessEnabled` is `false` or
`articlesRemainingThisMonth <= 0` — explain to the user that those are
quota-blocked or inactive.

### 2. For each remaining blog, prepare 3 fresh suggestions

For each blog (parallelizable), do the following.

**2a.** Pull the most recent published titles for novelty checks:

```bash
curl -sS -X POST https://autopod.co/api/v1 \
  -H "Authorization: Bearer $AUTOPOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"listRecentArticles","tenantId":"<BLOG_ID>","limit":20}'
```

Note: `tenantId` is required only when using an account (`apoa_…`) key.
Tenant keys ignore it.

**2b.** Look at any pending suggestions already queued for today:

```bash
curl -sS -X POST https://autopod.co/api/v1 \
  -H "Authorization: Bearer $AUTOPOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"listSuggestions","tenantId":"<BLOG_ID>","limit":10}'
```

**2c.** Decide whether the existing pending suggestions are diverse enough
versus the last 20 published titles. If you can pick 3 strong, distinct
suggestions from what's already pending, do that. Otherwise call:

```bash
curl -sS -X POST https://autopod.co/api/v1 \
  -H "Authorization: Bearer $AUTOPOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"generateSuggestions","tenantId":"<BLOG_ID>","count":5}'
```

The server already deduplicates against the last 50 published titles + last
50 pending suggestions, but YOU are the final judge. Reject any suggestion
that is essentially a rewording of a recent title and pull another from the
returned list (or generate again — at most once).

End up with exactly 3 suggestions per blog: keep their `id`, `title`,
`prompt`.

### 3. Present the menu to the user

Show one block per blog. Use this exact shape so the user can answer with
just numbers:

```
Visual Field Test  (28 articles left this month)
  1. How LASIK Affects Long-Term Night Vision
  2. The Real Difference Between Glaucoma Tests at the Optometrist
  3. Why Reading on a Phone Makes Eyes Feel "Stuck"

Cooking With Less Salt  (3 articles left this month)
  1. ...
  2. ...
  3. ...
```

End with one line:

> Reply with the number you want for each blog (e.g. `vft: 2, salt: skip`),
> or just say `skip all` / `1, 1, 2` if blogs are listed in order.

Accept any of these answer styles:
- `1, 2, skip` — positional, in the order you listed blogs.
- `vft: 2, salt: 1` — named, using slug or short name.
- `2` — single blog only.
- `skip <name>` or `skip all` — skip explicitly.
- A natural-language answer; map it carefully and confirm before acting.

If the user's answer is ambiguous, ask one clarifying question — don't act.

### 4. Trigger autopilot for each picked blog

For each `(blog, suggestionId)` the user picked:

```bash
curl -sS -X POST https://autopod.co/api/v1 \
  -H "Authorization: Bearer $AUTOPOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"runAutoPilot","tenantId":"<BLOG_ID>","suggestionId":"<SUGGESTION_ID>"}'
```

Capture the returned `jobId` for each blog. Keep them in your scratchpad as:

```
[
  { "blogId": "tenantA", "blogName": "Visual Field Test",
    "title": "How LASIK …", "jobId": "abc123" },
  ...
]
```

If `runAutoPilot` returns 429, the blog is out of quota — surface that and
move on. Don't retry.

### 5. Confirm + schedule the 2-hour review

Reply to the user with a short summary:

```
Triggered 2 articles, skipped 1.
✓ Visual Field Test → "How LASIK …"
✓ Cooking With Less Salt → "..."
- Sourdough For Beginners → skipped

I'll check back in 2 hours and report what shipped.
```

Then either:
- Schedule a follow-up task for `now + 2h` that re-invokes this skill with
  the saved jobId list (if your runtime supports it), OR
- Ask the user to set a 2-hour reminder, and persist the jobId list where
  the next invocation can find it (note in the conversation, memory, etc.).

---

## 2-hour review flow

Triggered 2 hours after step 5 (or whenever the user asks "how did today's
articles go?").

For each blog you triggered today:

```bash
curl -sS -X POST https://autopod.co/api/v1 \
  -H "Authorization: Bearer $AUTOPOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"getJobStatus","tenantId":"<BLOG_ID>","jobId":"<JOB_ID>"}'
```

If you don't have the jobId list (e.g. starting fresh in a new session),
fall back to:

```bash
curl -sS -X POST https://autopod.co/api/v1 \
  -H "Authorization: Bearer $AUTOPOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"listJobs","tenantId":"<BLOG_ID>","sinceMinutes":180}'
```

Each job response contains both a top-level `status` and (for API-triggered
jobs) a `publish` block with the downstream pipeline:

```json
{
  "success": true,
  "job": {
    "id": "abc...",
    "status": "completed",
    "articleId": "art123",
    "articleSlug": "how-lasik-affects-long-term-night-vision",
    "publishError": null,
    "publish": {
      "articleStatus": "published",
      "imageError": null,
      "translationsExpected": 5,
      "translationsCompleted": 5,
      "translationErrors": [],
      "audioExpected": 1,
      "audioCompleted": 1,
      "audioErrors": []
    }
  }
}
```

Map each job using BOTH fields:

- `status: completed` + `publish.articleStatus: "published"` + zero errors
  → fully shipped. Note `articleId` + `articleSlug`.
- `status: completed` + `publish.articleStatus: "published_no_image"` →
  shipped but image generation failed; surface `publish.imageError`.
- `status: completed` + `publish: null` → an older job not from the API path
  (dashboard-triggered draft). Skip it from Open Claw reporting.
- `publish.translationsCompleted < publish.translationsExpected` AND no
  errors → translations still running, re-check in 15-30 min.
- Any non-empty `publish.translationErrors` / `publish.audioErrors` arrays
  → list each `lang` + `error` to the user verbatim.
- `publish.audioCompleted < publish.audioExpected` → podcast still being
  generated (or failed if `audioErrors` populated).
- `status: processing` / `pending` — still generating the article itself.
  Don't treat as failure unless it's been > 90 minutes.
- `status: failed` — surface the `error` field verbatim.
- `publishError` — finalization broke (article generated but couldn't
  publish). Surface verbatim.

Compose a single status report for the user. Use checkmarks per pipeline
stage so they know exactly what shipped vs what's still running:

```
Today's Open Claw run — review

✓ Visual Field Test
  "How LASIK Affects Long-Term Night Vision" — published.
    article: ✓   image: ✓   translations: 5/5 ✓   podcast: ✓
  Slug: how-lasik-affects-long-term-night-vision

⏳ Cooking With Less Salt
  "5 Ways To Replace Salt In Soup" — published, still finishing up.
    article: ✓   image: ✓   translations: 3/5 ⏳   podcast: ⏳
  I'll re-check in 30 minutes.

✗ Mountain Biking Blog
  Generation failed: "OpenAI API Error: 429 rate_limit_exceeded".
  You can retry from the dashboard, or I can re-trigger it with the same
  suggestion when you say so.

Nothing else queued today.
```

If anything is in `failed` state, or any `publish.*Errors` array is
non-empty, **always** tell the user — never silently swallow it. If the
user asked you to be 99.9% hands-free, the only way that works is by
reporting failures loudly.

---

## API reference (quick)

Endpoint: `POST https://autopod.co/api/v1`

Auth: `Authorization: Bearer <AUTOPOD_API_KEY>` where the key is either
`apod_…` (tenant) or `apoa_…` (account).

| action               | body fields                                            | notes |
|----------------------|--------------------------------------------------------|-------|
| `listBlogs`          | —                                                      | Tenant key returns 1 blog. Account key returns up to 50. |
| `listRecentArticles` | `tenantId?`, `limit?` (default 20, max 100)            | Published only. |
| `listSuggestions`    | `tenantId?`, `limit?` (default 5, max 20)              | Most-recent first. |
| `generateSuggestions`| `tenantId?`, `count?` (default 5, max 10)              | Server already dedups vs last 50 articles + 50 suggestions. |
| `runAutoPilot`       | `tenantId?`, `suggestionId?`                           | Picks the most recent suggestion if `suggestionId` omitted. Server-side autopilot: generate text → image → publish → translations → audio → Buzzsprout. Returns `jobId`. |
| `getJobStatus`       | `tenantId?`, `jobId`                                   | Returns `status` + `publish` block (articleStatus, imageError, translationsExpected/Completed, audioExpected/Completed, error arrays). |
| `listJobs`           | `tenantId?`, `sinceMinutes?` (default 180, max 10080), `limit?` (default 50, max 200) | Used for the 2h review. Summary includes total, completed, failed, inProgress, published, translationsExpected/Completed, audioExpected/Completed. |

`tenantId` is **required** for every action except `listBlogs` when using an
account key. With a tenant key, `tenantId` is ignored.

## Failure modes you must surface

- `401` → "Invalid API key. Did the user revoke it in the dashboard?"
- `403` "Tenant subscription is not active" → tell the user that blog needs
  a renewed/active plan.
- `429` "Monthly article limit reached" → tell the user which blog.
- `404` "No suggestions available" on `runAutoPilot` → run
  `generateSuggestions` first, then retry once.
- 5xx → tell the user the call failed, include the response body, do not
  retry more than once.

## Environment

- `AUTOPOD_API_KEY` — required. The user's `apod_…` or `apoa_…` key.
- `AUTOPOD_BASE_URL` — optional override of `https://autopod.co/api/v1`,
  useful for testing against `https://autopod-test-env.web.app/api/v1`.

If `AUTOPOD_BASE_URL` is set, use it everywhere instead of the prod URL.
