---
name: onei-operator
description: Operate the onei.ai AI directory — review pending app submissions, publish and curate AI skills and MCP servers, manage categories and tags, and feature content via the Onei operations REST API. Use when asked to run, moderate, curate, or publish content on onei.ai.
---

# Onei AI Operator

You can operate https://onei.ai — an AI app / skill / MCP-server directory — through its REST API.

## Setup

You need an API key (format `onei_` + 40 hex chars). It is provided by the site owner (minted at https://onei.ai/admin/api-keys). Send it on every request:

```
Authorization: Bearer onei_xxxxxxxx...
Content-Type: application/json
```

Base URL: `https://onei.ai/api/v1`

Every response is `{"ok": true, "data": ...}` on success or `{"ok": false, "error": {"code", "message", "issues"?}}` on failure. Rate limit: 120 req/min. Scopes: `read` (browse), `write` (create/update/moderate/feature), `admin` (delete + categories).

## Common operations

### Check site health

```bash
curl -s -H "Authorization: Bearer $ONEI_KEY" https://onei.ai/api/v1/stats
```

Returns counts of active/pending apps, skills, MCPs, users, reviews. A growing `apps.pending` number means the moderation queue needs attention.

### Moderate the app submission queue

1. List pending submissions:
   `GET /api/v1/apps?status=pending`
2. Inspect one in full (description, screenshots, provider):
   `GET /api/v1/apps/{slug}`
3. Judge it: real product, working URL, non-spammy description, appropriate category.
4. Approve or reject:
   - `POST /api/v1/apps/{slug}/approve` (publishes it; clears any prior rejection)
   - `POST /api/v1/apps/{slug}/reject` (rejects it: stays unpublished and drops out of the `status=pending` review queue; find rejected ones with `status=rejected`)

### Publish a new AI app

`POST /api/v1/apps` — images are given as remote URLs and sideloaded to the CDN:

```json
{
	"name": "Example AI",
	"url": "https://example.ai",
	"iconUrl": "https://example.ai/icon.png",
	"summary": "10-200 chars shown on cards",
	"description": "At least 50 chars of markdown",
	"categoryId": 1,
	"pricing": "freemium",
	"providerName": "Example Inc",
	"providerUrl": "https://example.ai",
	"screenshotUrls": ["https://example.ai/shot1.png"],
	"tags": ["transcription", "meetings", "macos"]
}
```

The slug is derived from `url` (e.g. `example.ai`) and is permanent. The app goes live immediately unless you pass `"pending": true`. Check `GET /api/v1/apps/categories` for valid `categoryId` values, and search first (`GET /api/v1/apps?status=all&q=...`) to avoid duplicates. Update with `PATCH /api/v1/apps/{slug}`; delete (admin scope) with `DELETE /api/v1/apps/{slug}`.

### Feature / rank content

- Feature an app and optionally set its discovery weight (0-100, higher = more prominent in "Trending"):
  `POST /api/v1/apps/{slug}/feature` with `{"featured": true, "weight": 80}`
- Feature a skill or MCP (pins it first in listings):
  `POST /api/v1/skills/{slug}/feature` / `POST /api/v1/mcps/{slug}/feature` with `{"featured": true}`

### Publish a new AI skill

`POST /api/v1/skills` with at minimum:

```json
{
	"name": "Code Review Assistant",
	"slug": "code-review-assistant",
	"summary": "10-300 chars shown on cards",
	"description": "Longer markdown description",
	"author": "Author Name",
	"content": "# The actual skill prompt/definition...",
	"contentFormat": "markdown",
	"tags": ["review"],
	"features": ["Finds bugs"],
	"categoryIds": [1],
	"isActive": true
}
```

Optional per-client usage docs: `usageClaudeDesktop`, `usageCursor`, `usageWindsurf`, `usageVscode`, `usageGeneric` (markdown strings), plus `exampleUsage`, `repositoryUrl`, `documentationUrl`, `version`, `license`, `iconUrl`.

Update with `PATCH /api/v1/skills/{slug}` (partial body). Delete (admin scope) with `DELETE /api/v1/skills/{slug}`.

### Publish a new MCP server

`POST /api/v1/mcps` — same meta fields as skills (no `content`), plus `npmPackage` and per-client install configs:

```json
{
	"name": "Filesystem MCP",
	"slug": "filesystem-mcp",
	"summary": "...",
	"description": "...",
	"author": "...",
	"npmPackage": "@modelcontextprotocol/server-filesystem",
	"installClaudeDesktop": {
		"command": "npx",
		"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"],
		"env": { "OPTIONAL": "vars" }
	},
	"installCustom": [{ "command": "docker", "args": ["run", "..."], "description": "Docker" }]
}
```

`installCursor`, `installWindsurf`, `installVscode` follow the same config shape.

### Tag an entry

Every app, skill and MCP server takes `tags: string[]` on create and update. Tags are the one facet
shared by all three catalogues: `/tags/{slug}` lists the apps, skills and MCP servers that carry a
tag, which no category page can do, so a well-chosen tag puts a new entry on a page that already has
readers.

- **Read the vocabulary before inventing a word**: `GET /api/v1/tags?minTotal=1&orderBy=popular`, or
  `?q=postgres` to check one. A tag nobody else uses is a page with one entry on it, served
  `noindex`.
- Values are free text. Case, spaces and punctuation are normalised to one canonical tag, and known
  synonyms fold automatically (`Postgres` → `postgresql`, `k8s` → `kubernetes`), so you cannot fork
  a tag by spelling it differently — only by choosing a different word.
- 3-6 tags is the useful range; 10 is the hard cap. Tag what the entry *is about* — a technology, a
  domain, a capability, a platform, a delivery trait — never its category or its pricing.
- Omitting `tags` on a PATCH leaves them alone. Sending `[]` clears them.

```bash
curl -X PATCH "$ONEI/api/v1/skills/pdf-forms" -H "authorization: Bearer $ONEI_KEY" \
  -H 'content-type: application/json' -d '{"tags": ["pdf", "document-processing", "ocr"]}'
```

### Curate the tag vocabulary (admin scope)

- `GET /api/v1/tags/{slug}` — the tag plus everything carrying it. Accepts a label as well as a slug.
- `PATCH /api/v1/tags/{slug}` `{"name"?, "summary"?}` — the summary is the tag page's lede and meta
  description; write one for any tag with a few entries behind it. The slug is not editable: it is
  the page's URL.
- `POST /api/v1/tags/{slug}/merge` `{"into": "better-tag"}` — fold a duplicate. Everything carrying
  the loser is re-tagged and the loser's page starts returning 404, so merge towards the word the
  catalogue already uses.
- `DELETE /api/v1/tags/{slug}` — removes the tag and unlinks it everywhere. Prefer a merge.

### Manage categories (admin scope)

- List: `GET /api/v1/{apps|skills|mcps}/categories`
- Create: `POST /api/v1/skills/categories` with `{"name", "slug", "summary", "iconName"?}` (app categories take `{"name", "summary"}` only)
- Update: `PATCH /api/v1/{skills|mcps}/categories/{slug}`
- Delete: `DELETE /api/v1/{skills|mcps}/categories/{slug}` — refused (409) while the category still has entries.

### Publish the weekly issue

Onei AI Weekly is one issue per ISO week combining the week's AI news (external sources) with the
apps/skills/MCP servers released that week (internal links).

```bash
# 1. everything needed to write it, in one call — including whether it already exists
curl -s -H "Authorization: Bearer $ONEI_KEY" "https://onei.ai/api/v1/posts/brief?week=2026-W31"
# 2. create the draft
curl -s -X POST -H "Authorization: Bearer $ONEI_KEY" -H "Content-Type: application/json" \
  https://onei.ai/api/v1/posts -d @issue.json
# 3. run the gate until it is clean
curl -s -X POST -H "Authorization: Bearer $ONEI_KEY" \
  https://onei.ai/api/v1/posts/ai-weekly-2026-w31/validate
# 4. publish
curl -s -X POST -H "Authorization: Bearer $ONEI_KEY" -H "Content-Type: application/json" \
  https://onei.ai/api/v1/posts/ai-weekly-2026-w31/publish -d '{}'
```

The gate fetches every URL the issue cites before it will publish, so a citation that does not
resolve blocks publication rather than shipping. See `reference.md` for the full check list and the
payload schema. The editorial standard — what belongs in an issue and how to write it — is the
`onei-weekly` skill.

## Rules of thumb

- Slugs are permanent URLs — pick lowercase-kebab-case and don't change them after publishing.
- Never approve an app without fetching its detail first; check that the URL looks legitimate.
- Prefer `PATCH` with only the changed fields over resending the whole object.
- Summaries must be 10-300 characters (apps: 10-200); descriptions at least 10 (apps: at least 50).
- Edge caches purge automatically after each write — no extra step needed; public pages may lag a few minutes at most.
- On `422`, read `error.issues` — it contains per-field validation messages.
