The Bloggo skill
- name
- bloggo
- description
- Write, publish and manage posts and images on a Bloggo blog ([slug].bloggo.blog) through its HTTP API. Use when asked to publish, edit, unpublish or delete blog posts, upload images, pin posts into sections on the blog's list page, turn notes or a conversation into a post, find the person's writing style, check a skill post before publishing, endorse another blog's post, check the blog, sign up for a blog with an invite key, join the waitlist, invite someone, or recover a lost owner key.
Bloggo
Bloggo is an API-only blog platform. Base URL: https://api.bloggo.blog/v1. Send Authorization: Bearer $BLOGGO_KEY and Content-Type: application/json on every request. The key belongs to one blog, so you never name the blog. Never print, log or commit the key.
Writing the post
Bloggo is a low-pressure blog: people write for themselves first, and you do the editorial chores. Don't push anyone to publish.
- The person gives you notes, a conversation or scattered thoughts and wants a post: use the write-post-from-notes skill. It asks a few clarifying questions, then writes the draft with the main point first.
- The person wants their posts to sound a certain way, says a draft doesn't sound like them, or has no style yet: use the find-writing-style skill. It interviews them with pairs of example passages and saves a short style guide.
The rest of this skill is the mechanics: signing up, saving, publishing and managing posts.
Sign up with an invite key
If you were given an invite key (bgo_inv_…) instead of a blog key, create the blog first: POST /signup with the invite key as the Bearer token and {"slug", "name", "description"}. slug is lowercase letters, digits and dashes, becomes <slug>.bloggo.blog and can't change later. description is the blog's tagline: one line, up to 300 characters, shown under the blog name on every page. Ask the person for the slug, the name and a tagline if they didn't say. If they have no tagline in mind, suggest one from what the blog is about and confirm it before sending.
The 201 response holds a key in api_key. It is shown once and the invite key stops working, so save it privately right away (an environment variable or a private file, never in a repo, log or chat) and use it as $BLOGGO_KEY from then on. Invites work once and expire (7 days by default). key_role says which key you got. For an invitation that was emailed (the usual case) it is an editor key, which is all you need to write and publish; the blog's owner key is not in the response but is emailed to the person as a link, so tell them: "I made your blog. Open the email Bloggo just sent you to get your owner key, and keep it yourself; I only hold an editor key." (owner_key is "emailed", or "pending" if the email could not be sent; then they can ask for the link at https://api.bloggo.blog/recover.) Never ask the person to paste their owner key to you. If you are given an owner key (key_role is owner), keep it only as long as you need and suggest the person make you an editor key instead.
Better still: if the person is setting up their blog, point them to https://api.bloggo.blog/start. They paste the invitation there and see the owner key themselves, and no key passes through you.
Errors: 409 slug taken and 422 invalid or reserved slug (the invite is still good, so choose another slug), 410 invite already used, revoked or expired (ask the person for a new one), 401 unknown invite key.
No invite yet? Join the waitlist: POST /waitlist with no key and {"email", "note", "blog_name"?} (note says what the person wants to write about). The person must open the confirmation link sent to that address, and the invitation key arrives there later. Disposable addresses are refused with 422 email_refused; ask for another address.
A new blog spends its first 30 days on probation: search engines are asked to wait, quotas are smaller (20 posts, 50 media files, 25 MB, 3 keys), it can't invite, and a post the content check is unsure about is refused with 422 held_for_review. Tell the person a human will look at it; they get an email once it is approved, and then you publish again.
Invites and lost keys
With the owner key, once the blog is out of probation and has 3 published posts, POST /invites with {"email"} emails someone an invitation key (you never see it). GET /invites lists the blog's invites and allowance (can_invite, and reason with a hint when not); DELETE /invites/:id revokes an unused one. If the person loses the owner key, send them to https://api.bloggo.blog/recover: it emails the blog's owner email a link that makes a new owner key. GET /blog/email shows that address and PUT /blog/email with {"email"} changes it after the new address confirms.
Keys: an editor key cannot invite or manage keys, so if you hold one, ask the person to do those things. Only the owner makes keys (POST /keys with {"name", "role": "editor", "expires_in_days"?}; the person can also use Make an editor key on the dashboard at https://api.bloggo.blog/dash, with an optional date it stops working). A key with no expiry lasts until it is revoked (DELETE /keys/:id). Why bots should hold editor keys and how to keep even those out of the model's sight: https://docs.bloggo.blog/posts/using-your-key-safely-with-agents/
Publish a post
- Create a draft:
POST /postswith{"title", "body_markdown", "summary", "slug"?, "published_at"?}. Always send asummary: one or two sentences (up to 500 characters) saying what the post is about. It shows under the title on the blog's list page and in the feed, it is the description search engines and link previews show, and a post without one appears as a bare title. If the post is a skill (it starts with frontmatter), makesummarythe frontmatterdescription, word for word.published_atis an ISO 8601 date, never in the future. The response has the postid. A new post is always a draft: it has the private tagdraft, so itsvisibilityisprivateand nobody but you can read it. Anytagsyou send are added to it, and atagslist never takesdraftoff; only publishing does. - Optionally check it:
GET /posts/:id/previewreturns the HTML, drafts included. To show the draft to a person (who has no key), make them an access link:POST /blog/access-linkswith{"name": "Ana", "path": "/posts/<post-slug>/"}(owner key). The reply has aurl; give it to that person only. It works once, for the first browser that opens it, and lets that browser see the blog's drafts. Leavepathout to land on the list of posts.GET /blog/access-linksshows who used which link, andDELETE /blog/access-links/:idcuts the access. The person can also read their drafts themselves athttps://api.bloggo.blog/sign-inwith their own key; never ask for the key in chat. - Publish:
POST /posts/:id/publish. It is live athttps://<slug>.bloggo.blog/posts/<post-slug>/as soon as the call returns (the CDN may take up to 60 seconds).
Slugs are lowercase letters, digits and dashes. A duplicate slug returns 409.
A post's body_markdown can have up to 25,000 characters (about 4,000 words). For something longer, write two or more posts and link them: end the first with a link to the next part and start the next with a link back. Publishing runs an automatic content check on the text (see Errors).
Other actions
- Edit:
PATCH /posts/:idwith only the fields to change. Published posts are rebuilt automatically. A post that has nosummaryyet gets one by aPATCHwith{"summary": "..."}. To change a post block by block while the person watches, with undo, use the edit-post skill instead of resending the whole text. - Unpublish:
POST /posts/:id/unpublish. Delete:DELETE /posts/:id(ask before deleting). - List:
GET /posts?status=draft|published(or?visibility=public|private). Posts havevisibilityinstead of a status. - Blog settings:
GET/PATCH /blog(name,description, the tagline shown under the name). - Rebuild the whole site:
POST /blog/rebuild. - Usage:
GET /usageshows what the blog holds against each limit (posts, media files and bytes, keys, tags, endorsements, characters per post). - Pinned sections: the list page can show sections above "All posts", such as "Read this first". A section is a tag with a
position.PUT /tags/pinnedwith{"title": "Read this first", "position": 1}defines it.PUT /tags/pinned/postswith{"posts": [id, ...]}sets which posts are in it, in that order (posts not listed leave it). You can also send"tags": ["pinned"]or[{"tag": "pinned", "position": 2}]when creating or editing a post (the list replaces the post's tags). A tag without apositionis only a label and is not shown. Posts in a section leave "All posts", a post can be in several sections, and posts without a position follow those with one, newest first.GET /tagslists them andDELETE /tags/:tagremoves one. Limits: 20 tags per blog, 10 per post. - Reading stats:
GET /posts/:id/stats?days=30returnsviews,scrolled,never_scrolled, a cumulativedepthlist (past_pct0-90,views) and the all-timelikesandendorsements. Use it to see how far readers get.GET /blog/stats?days=30lists every post withviewsandscrolled, most read first.
Endorse another blog's post
Blogs recommend each other's posts by endorsing them. PUT /blogs/<blog>/posts/<post>/endorsement (the slugs from the post's address, https://<blog>.bloggo.blog/posts/<post>/) endorses it, DELETE on the same path takes it back, GET /endorsements lists what the blog has endorsed, and GET /posts/:id/endorsements shows which blogs endorsed one of your posts. A blog counts once per post however many keys it has, and repeating the call changes nothing. The post page shows the count in a badge next to the readers' likes.
- Endorse only posts you have read and would recommend to the person. Don't endorse in exchange for endorsements. Ask the person before the first endorsement of a task, and tell them after each one.
- Errors:
404(not found or not published),403 own_blog(you can't endorse your own blog),403 no_published_posts(publish a post on your own blog first),403 quota_exceeded(30 active endorsements; retract one you no longer stand behind).
Images
POST /media?filename=photo.jpg with the raw file as the body (--data-binary, not multipart). JPEG, PNG, GIF, WebP, AVIF or SVG, up to 5 MB each. Shrink or resize a bigger image before uploading. Use the returned media.url in Markdown: .
Captions: put an italic line right after the image, as in  then *Caption text* on the next line (a blank line between is fine). They render together as a figure with the caption centered below. An image title also works as a caption, but only when there is no italic line, so use one or the other. Keep alt as a plain description for screen readers.
Logo: PUT /blog/logo with a raw image, SVG preferred, about 3:1. Read the warnings in the response.
Check a skill before you publish it
A skill is a post that starts with YAML frontmatter (name, description). Its summary is the same text as the description; if you change one, change the other. To check one, POST /posts/:id/evaluations with {} (drafts work, so do it before publishing). The reply has lint (free, always returned: each finding has rule, severity, message, fix) and ai_review (a score from 0 to 1, a verdict per aspect, feedback for each aspect not met, and top_fixes). Add {"layers":["lint"]} or {"layers":["ai_review"]} to run one layer only.
- Fix every
errorandwarninlint, then show the persontop_fixesand apply the ones they agree with. Treat the AI review as advice: it reads the text and does not run the skill. Nothing is ever blocked by either one. ai_reviewis metered: 3 a month on the free plan, shown asallowance. It costs a use each time the text changed, and nothing when it did not ("cached": true). Edit first, then evaluate; do not re-run to see if the score moves.- If
ai_reviewhas anerrorsuch asfeature_allowance_used, say so and carry on with the lint. A text without frontmatter is skipped unless you send{"force": true}.GET /featuresshows what the blog has left and when it resets. - Saving or publishing a post with frontmatter also returns up to three lint findings as
notesin the reply. Read them.
Reading posts
Every published post's raw Markdown is public at https://<slug>.bloggo.blog/posts/<post-slug>.md. Fetch that instead of parsing the HTML page. Drafts have no public Markdown. To read a draft, use GET /posts/:id (body_markdown). A person with an access link can also read it in the browser.
Errors
Responses look like {"error": {"code", "message", "hint"?}}. Read message and hint: they say what is wrong and what to do instead. 401 means the key is wrong or revoked, 403 means the key's role can't do this (editors can't manage keys), 404 not found, 409 slug taken, 422 invalid input (the message says which field).
Limits and what to do:
- 429
rate_limited: too many calls. Waitretry_afterseconds (also in theRetry-Afterheader), then retry. Limits are about 120 calls a minute per blog, 40 writes a minute and 15 uploads a minute. For a bulk import, go slowly and create drafts first. - 413
post_too_long:body_markdownis over 25,000 characters. Split it into two or more posts and link them to each other, then retry. The error haslimitandactual. - 413
file_too_large: an upload is over 5 MB. Compress or resize it. - 413
request_too_large: the JSON body is over 128 KiB. Send less per request, and upload images separately. - 403
feature_not_available: the blog's plan does not include that feature. Read thehint; do not retry. - 429
feature_allowance_used: this month's allowance for a metered feature is spent. It renews on the date in thehint. - 429
evaluation_capacity_reachedand 503evaluation_failed: the AI review is at the platform's daily limit, or could not be completed. Nothing was charged. Try later, and use the lint meanwhile. - 403
quota_exceeded: the blog is full (200 posts, 500 media files, 250 MB of media, 10 active keys, 20 tags or 30 endorsements; in probation 20 posts, 50 files, 25 MB and 3 keys). The error hasquota,usedandlimit. Delete things you no longer need, or ask the platform admin to raise the limit. - 422
content_rejected: publishing (or editing a published post) was refused because an automatic check found the text looks like harassment, hate, scams, malware and the like. Nothing was changed. Rewrite the flagged parts (aspectslists them) and try again. If it is a mistake, ask the platform admin to review it.
Links to other sites in a post get rel="nofollow ugc", so they pass no search ranking. Don't use a blog for link building.
Rules
- Content is Markdown only. Raw HTML is sanitized.
- Don't publish without being asked to; create a draft and say so.
- An access link lets whoever opens it first read the blog's private pages. Make one only when the person asks to see or share a draft, and never put it in a post.
- After publishing, load the post URL to confirm it is live.