Call the Mastodon REST API with curl + jq. Two connector credentials are
injected: $MASTODON_BASE_URL (the instance, e.g. https://mastodon.social)
and $MASTODON_ACCESS_TOKEN. Every request sends the header
Authorization: Bearer $MASTODON_ACCESS_TOKEN.
Errors come back as JSON {"error":"<message>"} — show it verbatim. 401
("The access token is invalid") means the token is wrong/revoked → the user
must re-connect the Mastodon connector. Posting needs the token to have the
write (or write:statuses) scope.
Always confirm the token + account first (also gives the account id you
need to list your own toots):
curl -sS "$MASTODON_BASE_URL/api/v1/accounts/verify_credentials" \
-H "Authorization: Bearer $MASTODON_ACCESS_TOKEN" \
| jq '{id, username, acct, display_name, followers: .followers_count, statuses: .statuses_count}'
Post a toot
Confirm with the user before posting publicly. Default visibility to
unlisted unless they say post publicly; use public only on request.
STATUS_TEXT="Hello fediverse 👋 #introductions"
curl -sS -X POST "$MASTODON_BASE_URL/api/v1/statuses" \
-H "Authorization: Bearer $MASTODON_ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
--data-urlencode "status=$STATUS_TEXT" \
--data-urlencode "visibility=unlisted" \
--data-urlencode "language=en" \
| jq '{id, url, visibility, created_at}'
visibilityis one ofpublic,unlisted,private,direct.- Optional params:
spoiler_text(content warning),in_reply_to_id(reply),sensitive=true,language(ISO 639-1). Idempotency-Key(any unique string;uuidgenhere) prevents duplicate posts if the request is retried within ~1h. Use--data-urlencodeso hashtags, emoji and newlines in the text are encoded correctly.- Default post length is 500 chars (instance-configurable); longer text →
422 {"error":"Validation failed: Text ..."}.
List my recent toots + engagement
Use the id from verify_credentials:
ACCT_ID="ACCOUNT_ID" # from verify_credentials
curl -sS "$MASTODON_BASE_URL/api/v1/accounts/$ACCT_ID/statuses?limit=20&exclude_replies=true&exclude_reblogs=true" \
-H "Authorization: Bearer $MASTODON_ACCESS_TOKEN" \
| jq '.[] | {id, url, boosts: .reblogs_count, favs: .favourites_count, replies: .replies_count, created_at}'
limit max 40. Other filters: only_media, pinned, tagged=<hashtag>.
Delete a toot
curl -sS -X DELETE "$MASTODON_BASE_URL/api/v1/statuses/STATUS_ID" \
-H "Authorization: Bearer $MASTODON_ACCESS_TOKEN" | jq '{id, deleted: true}'
Deleting returns the status with its source text so you can delete-and-redraft.
404 {"error":"Record not found"} = not yours or already gone.
Attaching media (optional)
Upload each image/video via POST $MASTODON_BASE_URL/api/v2/media
(multipart/form-data, field file) to get a media id, then pass the ids as
media_ids[] when posting the status. See the docs for the full media contract:
https://docs.joinmastodon.org/methods/media/
Gotchas
- Federated: the API only ever targets
$MASTODON_BASE_URL(the user's own instance). There is no global endpoint — a token from instance A won't work on instance B. - Scopes: reading needs
read(orread:accounts/read:statuses); posting/deleting needswrite(orwrite:statuses). A403("This action is outside the authorized scopes") means the token lacks a scope. - Rate limits: Mastodon rate-limits per token; space out bulk posts or you'll
get
429. verify_credentialsreturns HTML in fields likenote; the plaintext source lives under thesourceobject.
Record the output
After you successfully publish and obtain the live result URL, call the built-in
publish_artifact tool ONCE so the user can track this deliverable in My Outputs:
publish_artifact(kind="message", channel="mastodon", title="<title>", url="<the REAL returned URL>", status="delivered")
Use the real returned URL — never fabricate one. Call it once per published item,
only after delivery is confirmed; skip it (or use status="failed") if publishing failed.
See _shared/artifacts.md.