Skip to main content
skill CX Codex

configuring-topics — Claude Code skill for Celigo Event Streams topics

A skill for setting up Celigo Event Streams topics: creating message channels, wiring publisher and listener steps, handling positions and deletion.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add celigo/ai --skill configuring-topics -a claude-code
Open on GitHub

What configuring-topics does

A topic in Celigo is a named message channel. One flow publishes records, any number of others read them, each from its own position. A message stays readable until the retention window ends, read or not.

The skill walks through the steps by number: creating and registering a topic, fields like retentionMs and externalId, the license and its quotas, the publisher step (adaptorType: "TopicImport") and the listener step (adaptorType: "TopicExport", type: "stream"), publishing, reading and purging messages, listener positions, diagnosing a listener that never runs, deleting and restoring. The exact calls depend on how you reach the account: the deployed Celigo MCP server has no topic tools, so creating, registering, publishing, reading, purging and deleting go through the CLI, the UI or the REST API, while the skill points to references/mcp.md and references/cli.md.

Publishing reaches every flow subscribed to the topic, and purging deletes every retained message for good. The skill tells you to confirm both with the user first. One thing to check before you start: without the Event Streams entitlement a topic write fails with topics_subscription_required, and the quota is only checked when a topic is created or restored, so nothing warns you as the account approaches the limit.

What it helps with

  • create a topic and register it on the integrations that need it
  • wire a publisher and a listener to the same topic
  • read retained messages without moving a listener's position
  • find out why a listener never runs
  • delete a topic or restore it from the recycle bin

Example prompts for the agent

  • create a topic with a 7-day retentionMs and register it on this integration
  • add a publisher step with adaptorType TopicImport for this topic
  • show the listener positions and tell me why one of them never runs

How to install configuring-topics

1Skills CLI (Vercel)

npx skills add celigo/ai --skill configuring-topics -a claude-code

2Manually: put the files in place

~/.claude/skills/configuring-topics/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/configuring-topics/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about configuring-topics

How do I install configuring-topics in Claude?

Run npx skills add celigo/ai --skill configuring-topics -a claude-code. The skill lands in ~/.claude/skills/configuring-topics/SKILL.md.

What does configuring-topics do?

It guides you through Celigo Event Streams topics: creating and registering them, publisher and listener steps, publishing, reading and purging messages, listener positions, and deletion.

Do I need a license to use topics?

Yes, Event Streams is licensed. Without the entitlement a topic write returns topics_subscription_required, and message calls return 403.

Source text

SKILL.md · MIT
---
name: configuring-topics
description: Configure Celigo Event Streams topics -- durable message channels that let one flow publish records and any number of other flows consume them, each from its own position. Covers creating and registering topics, retention and licensing, publisher and listener steps, publishing, reading and purging messages, listener positions, and delete rules. Use when creating or registering a topic, wiring a topic publisher or listener, publishing or inspecting messages, diagnosing a listener that never runs, or deleting a topic.
---

<!-- TIER:1 -->

# Configuring Topics (Event Streams)

A topic is a **named, durable message channel**. Publishers append messages; every listener receives all of them, reading from its own position at its own pace; and a message stays readable until the topic's retention window ends, whether or not anything read it. A topic decouples the flow that produces an event from the flows that consume it: add a consumer without touching the producer, pause one consumer while the others carry on, and let it catch up from where it stopped.

A topic is an account resource that lives in one environment, outside any integration -- like a lookup cache. Flows use it through two steps, neither of which takes a connection:

- **Publisher** -- an import with `adaptorType: "TopicImport"`. Every record it receives becomes one message.
- **Listener** -- an export with `adaptorType: "TopicExport"` and `type: "stream"`, the page generator of a flow that runs when messages arrive.

Concerns when working with topics:

- **The topic** -- name, retention, the license that gates it, and registration on the integrations whose flows use it
- **Publishing** -- a publisher step in a flow, API, or Tool, or a direct publish call
- **Listening** -- a listener step, and the flow-enable transition that subscribes it
- **Messages** -- reading what is retained (never moves a listener) and purging it (irreversible)
- **Positions** -- each listener's offset on the topic: where its next run resumes

## How to Run the Steps

This skill names each step by what it reads or changes; the exact calls depend on how you reach the account. Read the one reference that matches your tools -- both follow the step numbers of How to Build an Event Stream below, then the headings of Messages and Positions, Diagnosing a Listener That Never Runs, and Delete, Restore, and Copies:

- **Celigo MCP tools** (`upsert_export`, `upsert_import`, `upsert_flow`, `patch_flow`, ...) -- [references/mcp.md](references/mcp.md). The deployed server has no topic tools: it writes the listener and publisher steps and their flows, while creating, registering, publishing to, reading, purging, and deleting topics take the CLI, the Celigo UI, or the REST API.
- **The `celigo` CLI** -- [references/cli.md](references/cli.md)

Listing topics and reading messages, positions, dependencies, and the audit log only read. Creating, updating, registering, deregistering, and deleting a topic write to the account. Publishing reaches every flow subscribed to the topic -- each one processes the message -- and purging deletes every retained message for good, so confirm both with the user first.

## When to Use a Topic

| Need | Use |
|---|---|
| One event, several independent consumers | A topic -- each listener flow receives every message |
| Hand records from one flow to others | A topic -- chaining with `_runNextFlowIds` passes no records ([building-flows > Configure chaining](../building-flows/SKILL.md#10-configure-chaining-if-needed)) |
| A consumer that can be paused and catch up | A topic -- a listener whose flow is disabled keeps its position and resumes from it, as long as the messages are still retained |
| Enrich records mid-flow from published data | Not a topic -- a listener can't be a lookup step. Listen in its own flow, or keep the reference data in a [lookup cache](../configuring-lookup-caches/SKILL.md) |
| React to events in an outside system | A listener on that system (webhook, NetSuite or Salesforce listener, CDC) -- [configuring-exports > Listeners](../configuring-exports/SKILL.md#listeners). An outside system can also publish to a topic directly, with an API token scoped to it |

## Quick Reference

### Topic Fields

| Field | Rules |
|---|---|
| `name` | Send one: the API saves a topic without a name, and it can't be told apart in lists. Up to 200 characters. Names aren't unique -- steps bind the `_id`, so a rename never breaks one |
| `description` | Up to 5,120 characters; display only |
| `retentionMs` | Milliseconds a message stays readable, read or not. Default `604800000` (7 days); at least `3600000` (1 hour); at most `7776000000` (90 days) and the license's `topics.retentionCeilingDays`. Shortening it drops messages older than the new window |
| `externalId` | Your own key, to find the topic again without storing its id (a list filter) |
| `draft` | Set only on create: the topic deletes itself 48 hours later unless an update sets `draft` to `false` |
| `__integrationIds` | Create only: registers the new topic on these integrations. Never stored or echoed back; a registration that fails is reported in `__failedIntegrationRegistrations`, and the topic is still created |

Only `name` and `description` can be changed on their own. Any other change is a full update, and a full update clears `description` and `externalId` and resets `retentionMs` to 7 days when the body leaves them out -- always send the complete topic.

### License and Quota

Event Streams is licensed. The account license carries `topics.enabled`, `topics.topicsAllowed` (how many topics the account may hold), and `topics.retentionCeilingDays`. Listing topics works on every account; creating or updating one, and every message operation, needs the entitlement -- without it they fail with `topics_subscription_required` (422 on topic writes, 403 on message calls). The quota is checked when a topic is created or restored from the recycle bin (`topics_limit_exceeded`), nothing warns as the account approaches it, and topics in the recycle bin don't count.

### The Two Steps

| Step | Resource | Required | Defaults | Schema |
|---|---|---|---|---|
| Publisher | import, `adaptorType: "TopicImport"` | `topic._topicId` | `topic.batchSize` 1000 | [configuring-imports topic.yml](../configuring-imports/references/schemas/topic.yml) |
| Listener | export, `adaptorType: "TopicExport"`, `type: "stream"` | `topic._topicId` | `topic.startPosition` `latest`, `topic.maxWaitTimeMs` 30000 | [configuring-exports topic.yml](../configuring-exports/references/schemas/topic.yml) |

`topic._topicId` is the topic's `_id`, never its name. Neither step takes a `_connectionId`: with one, the save validates the step as that connection's adaptor and fails. The step rules live in [configuring-exports > Listeners](../configuring-exports/SKILL.md#listeners) and [configuring-imports > Topic Imports](../configuring-imports/SKILL.md#topic-imports-event-streams); this skill covers the topic they share.

### Schema Index

The listener and publisher step schemas are the `topic.yml` files linked under The Two Steps. The topic's own request and response shapes -- the topic, publish and its per-message results, a page of messages, a listener's position, and the purge result -- are on the API reference's [Topics](https://developer.celigo.com/api/api-reference/data-stores/topics.md) page (agent-readable markdown).

## Related Skills

- [configuring-exports > Listeners](../configuring-exports/SKILL.md#listeners) -- the topic listener step (`TopicExport`)
- [configuring-imports > Topic Imports](../configuring-imports/SKILL.md#topic-imports-event-streams) -- the publisher step (`TopicImport`)
- [building-flows > Create disabled, test, enable](../building-flows/SKILL.md#11-create-disabled-test-enable) -- enabling a flow is what subscribes its listener
- [configuring-imports > Writing Exactly Once](../configuring-imports/SKILL.md#writing-exactly-once) -- idempotent writes in listener flows
- [managing-integrations](../managing-integrations/SKILL.md) -- registration on integrations, cloning, and promotion across environments
- [managing-api-tokens > Decide the scope](../managing-api-tokens/SKILL.md#3-decide-the-scope) -- tokens that can publish to and read only named topics
- [configuring-lookup-caches](../configuring-lookup-caches/SKILL.md) -- the other environment resource registered on integrations; use it for enrichment

<!-- TIER:2 -->

## How to Build an Event Stream

### 1. Plan publishers and listeners

- **The message.** What one message stands for (an order, an order line), and its shape: any JSON object up to 1 MiB serialized. A listener's flow receives each message as its record, exactly as it was published -- no envelope, no timestamp.
- **Who publishes.** A publisher step in a flow, an API, or a Tool, or a direct publish call -- from outside Celigo too, with an API token scoped to the topic.
- **Who listens.** One listener flow per consumer. Every listener receives every message, from its own position.
- **Retention.** At least the longest time a listener may be paused or failing before someone fixes it. Messages older than the window are dropped whether or not anything read them; a listener paused longer than that never sees them and resumes from the earliest message left.
- **Duplicates.** The same message can be processed more than once (step 7), so plan an idempotent write for every listener flow.

### 2. Check for an existing topic

List the account's topics, filter by `externalId`, or list what one integration has registered. Names aren't unique: before reusing a topic, confirm it by `_id` and description, and see what already publishes to and listens on it (its dependencies).

### 3. Create the topic and register it

Create the topic with a name, a description of what it carries, and a retention period; add an `externalId` if something must find it again by key. Flows publish and listen on topics registered on their integration. Register the topic on create (`__integrationIds`) or afterwards through the integration's topic registration -- or let the flow do it: saving a flow with a step bound to a topic registers that topic on the flow's integration. Registration adds no alias, unlike a lookup cache's.

### 4. Build the publisher

An import with `adaptorType: "TopicImport"` and `topic._topicId`, with no connection and no create, update, or upsert logic: every record becomes one message.

- Without a mapping, the message is the record as the step received it; a mapping reshapes it.
- With `oneToMany: true` and `pathToMany`, each element of that array is published as its own message, carrying the rest of the parent record (without the array) under `_PARENT`.
- `topic.batchSize` (default 1000) is how many records go per publish request; lower it when messages are large.

The step can publish from a flow, from an API, from a Tool reached through a `ToolImport` step, or on its own through an import invoke. A flow or API test run publishes nothing -- import steps are mocked there -- while an invoke publishes for real to every subscribed flow.

### 5. Build the listener and its flow

An export with `adaptorType: "TopicExport"`, `type: "stream"`, and `topic._topicId`, with no connection, no `cdc` object, and no `isLookup`. Make it the flow's page generator and create the flow with `disabled: true` and no schedule.

- **`topic.startPosition`** -- `latest` (the default) delivers only messages published after the listener subscribes; `earliest` first replays every message the topic still holds, the moment it subscribes. It's read when the listener is created and whenever its `_topicId` changes, so to reprocess retained history create a new listener with `earliest`.
- **`topic.maxWaitTimeMs`** -- how long the listener collects messages before handing the flow a batch, at least 1000 (default 30000). The first run after a publish starts roughly that long after it; lower it for latency on a quiet topic. The export's `pageSize` caps the records per batch.

Without `type: "stream"` the export still saves but runs as a batch export that never starts on a publish -- see [configuring-exports > Listeners](../configuring-exports/SKILL.md#listeners) for the repair.

### 6. Enable the flow to subscribe

The listener subscribes when its flow goes from disabled to enabled; within about half a minute its position appears (step 3 of Diagnosing a Listener That Never Runs) -- or, on a topic nothing has been published to yet, with the first publish, which the listener still receives. A flow created already enabled never subscribes: it doesn't run when messages are published, and its listener has no position until the flow is disabled and enabled again. Under `latest`, anything published before the subscription formed is skipped -- enable first, then publish. After repairing a listener, disable and re-enable its flow.

Then publish a test message (or invoke the publisher with one record) and confirm the flow ran.

### 7. Make every listener flow's writes idempotent

A message can reach the destination more than once, so key each listener flow's write on the message's business key ([configuring-imports > Writing Exactly Once](../configuring-imports/SKILL.md#writing-exactly-once)):

- An `earliest` listener replays every retained message when it subscribes.
- Every listener processes every message -- including a listener in a copy of the flow cloned within the same environment, which keeps the topic.
- A retried error re-sends its record ([building-flows > Duplicate Records](../building-flows/SKILL.md#duplicate-records)).
- A listener whose saved position expired or was purged resumes from the earliest message still retained.

## Messages and Positions

**Publish.** The body is `{"messages": [ {...}, ... ]}` -- each message one JSON object, at most 1 MiB (1,048,576 bytes) serialized; a bare array fails with 400 `invalid_messages`, and an empty array publishes nothing. A 200 means the body was valid, not that every message was published: read each `responses[].statusCode`. An oversized message fails on its own with 422 `topic_publish_event_too_large` while the rest of the batch publishes. The first publish to a new topic provisions its stream and takes a few seconds longer than later ones.

**Read.** Messages come back oldest first, at most 100 per page (default 50), each as `{timestamp, payload}`, where `payload` is the message as published. Follow `nextPageURL` until it's absent, and narrow by publish time with `time_gt` and `time_lte` (epoch milliseconds). There's no search. Reading never moves any listener's position.

**Purge.** Deletes every retained message and keeps the topic, its retention, its registrations, and its subscriptions; it can't be undone, and the topic's audit log records it as a `purge` of `messages`. Offsets keep counting after a purge, and enabled listeners receive the next messages as usual. Purge empties the topic; deleting it removes the topic -- choose as with [a lookup cache](../configuring-lookup-caches/SKILL.md#purge-vs-delete----data-vs-resource).

**Positions.** For one listener on one topic the position is `{hasCursor, offset}`, where `offset` is the next message the listener will read, counted from the start of the stream.

- `hasCursor: false` (offset `-1`) -- most often the listener has never subscribed (its flow hasn't gone from disabled to enabled since it was saved, it was saved without `type: "stream"`, or it doesn't listen to this topic), or nothing has ever been published to the topic: an enabled listener's position appears with the first publish, and it still receives that message.
- A disabled flow keeps its listener's position, and on re-enable the flow receives everything published meanwhile.
- While no message has expired or been purged, the retained message count minus `offset` is the listener's backlog.
- When the message at a listener's position is no longer retained -- it expired or was purged -- the listener still reports `hasCursor: true` with that offset, and its next run resumes from the earliest message still retained, whatever its `startPosition`.

## Diagnosing a Listener That Never Runs

1. **Read the listener export.** It needs `adaptorType: "TopicExport"`, `type: "stream"`, and the `_id` of the topic the publisher uses in `topic._topicId`. Without the type, repair the export as in step 5, then toggle the flow.
2. **Read the flow.** It must be enabled. A flow created enabled has never subscribed: disable it, then enable it.
3. **Read the listener's position.** `hasCursor: false` most often means it never subscribed (steps 1 and 2), or that the topic has never received a message (step 4) -- then the position appears with the first publish. `hasCursor: true` means it's subscribed.
4. **Read the topic's messages.** While nothing has expired or been purged, the retained count minus `offset` is what the listener hasn't read yet. Nothing unread, or no message with a `timestamp` after the flow was enabled, means nothing was published since it subscribed: check the publisher. Flow and API test runs publish nothing, and a publish answers 200 even when a message was rejected.
5. **Allow for the batch window.** A run starts about `maxWaitTimeMs` (default 30 seconds) after a publish. Then read the flow's runs and errors as for any flow ([troubleshooting-flows](../troubleshooting-flows/SKILL.md#diagnostic-workflow)).
6. **Records missing from runs.** Under `latest`, messages published before the subscription formed were skipped; messages that expired past `retentionMs` (or were purged) before the listener read them are gone, and it carried on from the earliest message left. To reprocess what is still retained, add a new listener with `startPosition: "earliest"`.

## Delete, Restore, and Copies

- **Delete is refused while anything binds the topic**: any export or import with its `topic._topicId` -- even one in no flow -- or a flow that contains one. The 422 `dependencies_not_deleted` names each blocker; read the topic's dependencies, then delete or repoint each one.
- **Deregistering is refused while a step in that integration binds the topic** (400 `not_allowed`). Deleting the topic needs no deregister first: the delete drops it from every integration it was registered on.
- **A deleted topic sits in the recycle bin for 30 days**, missing from lists and answering 404 on reads. Restoring it brings back its retained messages but not its registrations -- register it again. Purging it from the bin deletes it and its messages for good, and steps that bound it must be purged from the bin first.
- **A clone within the same environment keeps the topic.** A cloned export, import, or whole flow keeps `topic._topicId`, so the clone of a listener flow is one more consumer once enabled. A copy in another environment needs that environment's topic: after cloning into another environment, installing a template, or pulling changes, check each topic step's `topic._topicId`.

<!-- TIER:3 -->

## Pre-Submit Checklist

- [ ] The license has Event Streams enabled, and a create leaves the account within `topicsAllowed`
- [ ] `retentionMs` is between one hour and 90 days and within the license's `retentionCeilingDays`
- [ ] A full update of the topic carries `description`, `externalId`, and `retentionMs`
- [ ] The topic is registered on every integration whose flows publish or listen (or a flow save registers it)
- [ ] Listener: `adaptorType: "TopicExport"`, `type: "stream"`, `topic._topicId` set to the topic's `_id`; no `_connectionId`, `cdc`, or `isLookup`
- [ ] Publisher: `adaptorType: "TopicImport"`, `topic._topicId` set to the topic's `_id`; no `_connectionId`
- [ ] `startPosition` chosen deliberately -- `earliest` replays everything retained
- [ ] The listener flow was created with `disabled: true` and no schedule, and is enabled only when ready
- [ ] Every listener flow's write is idempotent
- [ ] Every publish result was checked per message

## Gotchas

1. **A listener flow created enabled never subscribes.** It doesn't run on a publish and its listener has no position until the flow goes from disabled to enabled.
2. **`latest` skips what came before the subscription.** Messages published before the flow was enabled -- or while a flow created enabled sat unsubscribed -- never reach that listener.
3. **`earliest` replays everything retained** the moment the listener subscribes, without any new publish. Make sure the flow's writes are idempotent first.
4. **A listener saved without `type: "stream"`** saves without error and never runs on a publish; a PATCH can't add the type.
5. **A full update erases what it omits.** `description` and `externalId` are cleared and `retentionMs` goes back to 7 days.
6. **A publish 200 isn't per-message success.** Read every `responses[].statusCode`; a message over 1 MiB fails alone.
7. **Test runs publish nothing; an invoke publishes for real**, to every subscribed flow.
8. **Purge is irreversible.** It keeps subscriptions and offsets keep counting -- there's no rewind.
9. **Names aren't unique.** Bind and look up by `_id` (or `externalId`).
10. **Any binding blocks the delete**, even an export or import that's in no flow -- and once the topic is in the recycle bin, the deleted steps that bound it block its purge until they're purged.
11. **Restore doesn't re-register.** Register the restored topic on each integration again.
12. **A cloned listener flow is another consumer.** Same-environment clones keep `topic._topicId`.

## Common Errors

| Error | Cause | Fix |
|---|---|---|
| `topics_subscription_required` (422 on create or update, 403 on message calls) | The license doesn't include Event Streams | Check the license's `topics.enabled`; the account owner contacts Celigo |
| 422 `topics_limit_exceeded` | The account holds `topicsAllowed` topics | Delete or reuse an unused topic (topics in the recycle bin don't count), or have the account owner ask Celigo to raise the limit |
| 422 `min` on `retentionMs` | Under one hour (`3600000`) | Use at least `3600000` |
| 422 `topics_retention_exceeds_entitlement` and `max` | Over the license ceiling or 90 days | Stay within `retentionCeilingDays` and `7776000000` |
| 422 `... is not a whitelisted property` on a PATCH | Only `/name` and `/description` patch | Send a full update with every field |
| 400 `invalid_messages` on publish | The body isn't `{"messages": [...]}` | Wrap the messages in a `messages` array |
| `topic_publish_event_too_large` in `responses[]` | One message over 1 MiB serialized | Trim or split that message; the rest were published |
| 422 `invalid_ref` "... is not a valid topic._topicId" | Not a topic `_id` in this environment -- often the name | Look the id up by name or `externalId` |
| 422 `missing_required_field` "http subschema not defined" | A `_connectionId` on a topic step | Remove the connection |
| 422 `not_supported` "Lookup functionality is not available for Stream Export" | `isLookup: true` on a listener | Listen in a separate flow; enrich from a lookup cache |
| 422 `dependencies_not_deleted` on delete | An export, import, or flow still binds the topic | Delete or repoint each one named, then retry |
| 400 `not_allowed` on deregister | A step in the integration binds the topic, or it isn't registered there | Repoint or delete the steps first |
| 404 "already in the Recycle Bin" | The topic was deleted | Restore it, then register it again |
| Listener flow never runs | Not subscribed, nothing published, or the batch window | [Diagnosing a Listener That Never Runs](#diagnosing-a-listener-that-never-runs) |

© celigo · MIT · celigo/ai

Similar skills

All skills for Automation and no-code: n8n, Zapier, Make →

From the same set17

writing-sql Writes SQL for Celigo RDBMS exports and imports and helps debug errors in rdbms.query. Useful when an integration reads from or writes to Snowflake, Postgres, MySQL, SQL Server, Oracle, BigQuery or Redshift. CX Codex +1 2,900installs writing-mappings Writes field mappings and transforms for Celigo integrations: Mapper 2.0, Transformation 2.0, lookups and response mapping. CX Codex +1 3,000installs configuring-imports A skill for configuring Celigo imports, the destination step that writes records to external systems. Useful when you pick an adaptor type, set up lookups or wire an upsert. CX Codex +1 2,900installs configuring-exports A skill for configuring Celigo exports: connecting to an external system, picking an adaptor, delta exports, webhooks and lookup steps. CX Codex +1 3,000installs managing-stacks A skill that explains how to run Celigo hooks and wrappers on your own server or AWS Lambda, and when that is worth doing. CX Codex +1 2,700installs using-marketplace-templates Walks through finding and installing Celigo Marketplace templates and Integration Apps, and explains when to install the whole thing versus borrow one piece. CX Codex +1 2,700installs managing-api-tokens A skill for handling Celigo API tokens: creating them, scoping access, revoking and deleting. Useful when a script or pipeline has to authenticate into integrator.io. CX Codex +1 2,700installs managing-network-policies A skill for managing Celigo network policies: named IP allow and deny lists that restrict where MCP servers, API tokens and user sign-in can connect from. CX Codex +1 303installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor