Zumika

Developer docs

Zumika has three surfaces: an embed widget for showing a feed, a public JSON feed for building your own front end, and a REST API for managing feeds, sources and posts.

Embed widget

Place a container with the feed’s public key and load the script once per page. The widget renders into the page itself (no iframe), so it inherits your fonts and can be styled with CSS.

<div data-zumika-feed="FEED_PUBLIC_KEY"></div>
<script async src="https://zumika.limegreentangerine.net/embed.js"></script>

Design settings are saved against the feed in the dashboard. Override them per placement with attributes:

data-zumika-layout
waterfall, grid, carousel, reel (portrait carousel) or list. Ignored when the feed is split into sections.
data-zumika-limit
Posts per load, 1 to 50
data-zumika-manual
Skip automatic rendering; call Zumika.render() yourself

JavaScript API:

const wall = Zumika.render("#wall", {
  feed: "FEED_PUBLIC_KEY",
  layout: "grid",          // optional override
  limit: 9,                // optional override
  settings: { colors: { accent: "#0a66c2" }, radius: 0 },
});

wall.loadMore();           // fetch the next page
Zumika.init();               // render any new [data-zumika-feed] elements

el.addEventListener("zumika:loaded", (e) => e.detail.posts);
el.addEventListener("zumika:post-click", (e) => {
  e.preventDefault();      // cancel the popup and handle e.detail.post yourself
});

Every element carries a class starting zumika- (.zumika-card, .zumika-text, .zumika-btn). Colours are CSS variables on .zumika: --zc card, --zt text, --zm secondary text, --za accent, --zb border.

Authentication

The REST API is HTTPS only. Create an API key under API & plugins in a workspace and send it as a Bearer token. A key gives full access to that workspace, so keep it on the server and never in browser code.

curl https://zumika.limegreentangerine.net/api/v1/feeds \
  -H "Authorization: Bearer zumika_xxxxxxxxxxxxxxxx"

The key is also accepted as an X-API-Key header or an api_key query parameter. Request bodies are JSON. Wherever a feed ID is expected, the feed’s public key works too.

Feeds

GET/api/v1/feeds

List the workspace’s feeds with their post counts.

POST/api/v1/feeds

Create a feed.

name
Required. Shown only in the dashboard.
moderation
auto (default) or manual. Manual holds new posts as pending.

GET/api/v1/feeds/{id}

One feed, including its widget settings.

PATCH/api/v1/feeds/{id}

Update a feed. Send only what should change.

name
New name
moderation
auto or manual
settings
Object of widget settings: layout, sections, columnWidth, gap, radius, perPage, loadMore, click, showAuthor, showText, showStats, showDate, showNetwork, textLines, colors, customCss, autoplay (0, 3, 5 or 8 seconds), arrows, header (show, title, text, buttonLabel, buttonUrl), ratingSummary

DELETE/api/v1/feeds/{id}

Delete a feed with its sources and posts. This cannot be undone.

Sections

settings.sections splits a wall into rows, each with its own layout and filter: a Shorts carousel, a Vimeo carousel, then everything else. Featured sections are shown first and highlighted. A section with no posts is hidden. With an empty array the wall shows every post in settings.layout.

{
  "settings": {
    "sections": [
      { "title": "Shorts", "layout": "reel", "featured": true, "sourceIds": ["SOURCE_ID"], "perPage": 10 },
      { "title": "Latest videos", "layout": "carousel", "networks": ["youtube", "vimeo"] },
      { "title": "Everything else", "layout": "waterfall" }
    ]
  }
}
title
Heading shown above the row. Optional.
layout
waterfall, grid, carousel, reel or list
featured
true to show the section first, highlighted
networks
Network keys to include. Empty means all.
sourceIds
Source IDs to include. Empty means all.
perPage
Posts per load, 1 to 50
{
  "success": true,
  "feeds": [
    {
      "id": "cmf2k1x9a0001",
      "name": "Homepage wall",
      "public_key": "h3Jq9sLm2Xw_",
      "moderation": "manual",
      "created_at": "2026-11-13T10:24:00.000Z",
      "post_count": 184
    }
  ]
}

Sources

GET/api/v1/feeds/{id}/sources

List a feed’s sources, with the time and any error from the last fetch.

POST/api/v1/feeds/{id}/sources

Add a source. Zumika fetches it straight away and reports how many posts were added.

type
Required. A source type from the table below.
tag
The hashtag, username, URL or ID to collect. Not needed for account-level connected sources.
connection_id
Required for types that need a connected account.
interval_minutes
15, 30, 60 (default), 180, 720 or 1440

DELETE/api/v1/sources/{id}

Remove a source and the posts collected from it.

GET/api/v1/source-types

The table below as JSON. No key needed.

typeNetworktag
instagram_accountInstagramUses connection_id
instagram_hashtagInstagramHashtag + connection_id
instagram_mentionsInstagramUses connection_id
facebook_pageFacebookUses connection_id
x_userX / TwitterUsername
x_hashtagX / TwitterHashtag or search query
x_mentionsX / TwitterUsername
tiktok_accountTikTokUses connection_id
linkedin_companyLinkedInUses connection_id
youtube_channelYouTubeChannel ID or @handle
youtube_videosYouTubeChannel ID or @handle
youtube_shortsYouTubeChannel ID or @handle
youtube_playlistYouTubePlaylist ID
youtube_searchYouTubeSearch query
google_reviewsGoogle ReviewsPlace ID
reddit_subredditRedditSubreddit
reddit_searchRedditSearch query
bluesky_userBlueskyHandle
bluesky_hashtagBlueskyHashtag or search query
mastodon_hashtagMastodoninstance#hashtag
mastodon_userMastodon@user@instance
flickr_tagFlickrTag
flickr_userFlickrUser ID (NSID)
vimeo_userVimeoUsername
vimeo_searchVimeoSearch query
tumblr_blogTumblrBlog name
tumblr_tagTumblrTag
pinterest_userPinterestUsername
pinterest_boardPinterestusername/board-name
rss_feedRSSFeed URL

Posts

GET/api/v1/feeds/{id}/posts

List posts, pinned first, then newest first.

status
approved (default), pending, rejected or all
network
Filter by network key, for example instagram
source_id
Filter by source
limit
1 to 100, default 25
offset
Number of posts to skip

PATCH/api/v1/posts/{id}

Moderate a post.

status
approved, pending or rejected
pinned
true or false
text
Replacement text, or null to restore the original
{
  "success": true,
  "total": 184,
  "has_more": true,
  "posts": [
    {
      "id": "cmf2k4b7c00a3",
      "network": "instagram",
      "url": "https://www.instagram.com/p/…",
      "text": "Golden hour on the quay.",
      "author": { "name": "harbourcafe", "handle": "@harbourcafe", "avatar": "https://…", "url": "https://…" },
      "media_type": "image",
      "image": "https://zumika.limegreentangerine.net/media/…/….webp",
      "image_width": 1080,
      "image_height": 1350,
      "video": null,
      "likes": 212,
      "comments": 14,
      "views": null,
      "rating": null,
      "posted_at": "2026-11-12T17:02:11.000Z",
      "status": "approved",
      "pinned": false,
      "source_id": "cmf2k2p1m0007"
    }
  ]
}

media_type is none, image, video (a playable file in video) or embed (a player URL in video, for YouTube, Vimeo and TikTok).

Public feed JSON

GET/api/v1/public/feeds/{public_key}/posts

What the widget itself calls. No API key, CORS open, approved posts only. Use it to build a custom front end. Responses are cached for 60 seconds.

limit
1 to 50, default 12
offset
Number of posts to skip
networks
Comma-separated network keys, for example youtube,vimeo
sources
Comma-separated source IDs

Returns feed (name and widget settings), total, has_more and posts in the shape above.

Errors

Errors return { "success": false, "error": "…" } with one of these statuses:

401
Missing, wrong or revoked API key
404
The feed, source or post does not exist in this workspace
422
A value was missing or not allowed; the message says which
500
Something failed on our side; try again