2 Quickstart
A walkthrough of the zulipdata library’s core API: authenticating, listing public channels, pulling channel messages, and shaping them into a tablecloth dataset. Run this notebook end-to-end to confirm your setup works.
Credentials are read from ZULIP_EMAIL / ZULIP_API_KEY env vars or ~/.zuliprc — see Zulip’s API keys documentation for how to obtain them.
(ns zulipdata-book.quickstart
(:require
;; Zulipdata client -- Zulip REST API wrapper
[scicloj.zulipdata.client :as client]
;; Zulipdata pull -- paginated, cached channel history
[scicloj.zulipdata.pull :as pull]
;; Zulipdata views -- tablecloth projections of raw messages
[scicloj.zulipdata.views :as views]
;; Kindly -- notebook rendering protocol
[scicloj.kindly.v4.kind :as kind]
;; Tablecloth -- dataset manipulation
[tablecloth.api :as tc]))Authenticating
client/whoami calls /users/me and returns a summary of the authenticated identity:
(def me (client/whoami))me{:email "user138175@clojurians.zulipchat.com",
:full-name "Daniel Slutsky",
:user-id 138175,
:is-bot false,
:is-admin true,
:role 100}Listing channels
pull/public-channel-names returns every channel the bot can read — both fully public (login-gated) and the smaller subset that is web-public (readable without logging in).
(def public-channels (pull/public-channel-names))(count public-channels)178A prefix:
(take 5 (sort public-channels))("Cursive" "Defi" "Disorganized" "Emacs" "Graalvm")pull/web-public-channel-names returns just the web-public subset. Throughout this book we draw demo data from these channels so that message content can be shown without leaking anything login-gated.
(def web-public (pull/web-public-channel-names))web-public["announce"
"beginners"
"bubble-up"
"calva"
"clojars"
"clojure"
"clojure-europe"
"clojure-ohio"
"clojure-uk"
"clojurecivitas"
"clojurescript"
"events"
"general"
"gratitude"
"honeysql"
"jobs"
"news-and-articles"
"off-topic"
"project-announcements"
"scicloj-webpublic"
"slack-archive"
"sql"
"std.lang-dev"
"windows-clojure"
"xtdb"
"zulip"]Pulling messages from one channel
pull/pull-channels! walks forward through a list of channels in cached windows. The first run populates the disk cache; subsequent runs are served from cache. We pull clojurecivitas, a web-public channel.
(def pulled
(pull/pull-channels! ["clojurecivitas"]))(def message-count
(get-in pulled ["clojurecivitas" :message-count]))message-count273Flatten the cached windows into a single sequence of raw messages:
(def raw-messages
(pull/all-messages (get pulled "clojurecivitas")))(count raw-messages)273A single raw message — one map per Zulip message, with sender, topic, content, timestamps, reactions, and edit history:
(first raw-messages){:display_recipient "clojurecivitas",
:content
"**Public** channel created by @_**Timothy Pratley|550430**. **Description:**\n```` quote\n*No description.*\n````",
:sender_id 100006,
:client "Internal",
:submessages [],
:type "stream",
:sender_realm_str "zulipcore",
:stream_id 528764,
:content_type "text/x-markdown",
:id 538932511,
:is_me_message false,
:sender_email "notification-bot@zulip.com",
:sender_full_name "Notification Bot",
:recipient_id 1681040,
:timestamp 1757619820,
:flags ["read" "historical"],
:subject "channel events",
:reactions [],
:topic_links [],
:avatar_url
"https://static.zulipchat.com/static/images/static_avatars/notification-bot.36f721bad3d0.png"}Building a timeline view
views/messages-timeline projects raw messages into a tablecloth dataset with one row per message and simple-valued columns only:
(def timeline (views/messages-timeline raw-messages))(-> timeline
(tc/order-by :instant :desc))_unnamed [273 13]:
| :instant | :content | :last-edit-ts | :client | :channel | :stream-id | :edited | :content-length | :id | :sender | :sender-id | :timestamp | :subject |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-05-02T18:42:10Z | These days, we are exploring Babashka for data analysis and using Babqua for interactive data visualization. This time, we are looking into @**Gert Goet**’s Clojure Events Calendar Feed. | website | clojurecivitas | 528764 | false | 186 | 592473831 | Daniel Slutsky | 138175 | 1777747330 | Datavis in Babashka: analysing our calendar feed | |
| 2026-05-02T18:39:21Z | Datavis in Babashka: analysing our calendar feed | ZulipZapierApp | clojurecivitas | 528764 | false | 130 | 592473657 | Civitas | 942591 | 1777747161 | Datavis in Babashka: analysing our calendar feed | |
| by Daniel Slutsky | ||||||||||||
| 2026-05-02T16:31:38Z | lovely :smile: | ZulipElectron | clojurecivitas | 528764 | false | 14 | 592462084 | Timothy Pratley | 550430 | 1777739498 | Hello, Babashka | |
| 2026-05-02T15:01:33Z | Dark mode in mermaid works now, thanks @**Timothy Pratley**. | website | clojurecivitas | 528764 | false | 60 | 592454894 | Daniel Slutsky | 138175 | 1777734093 | Hello, Babashka | |
| 2026-05-01T17:15:48Z | https://github.com/scicloj/babqua/issues/1 | website | clojurecivitas | 528764 | false | 42 | 592324446 | Daniel Slutsky | 138175 | 1777655748 | Hello, Babashka | |
| 2026-05-01T17:13:13Z | :thumbs_up: | ZulipElectron | clojurecivitas | 528764 | false | 11 | 592323987 | Timothy Pratley | 550430 | 1777655593 | Hello, Babashka | |
| 2026-05-01T17:12:20Z | Kindly is handled in a very ad-hoc way at the moment: | website | clojurecivitas | 528764 | false | 130 | 592323846 | Daniel Slutsky | 138175 | 1777655540 | Hello, Babashka | |
| https://github.com/scicloj/babqua/blob/6f9f703/_extensions/bb/runtime.bb#L74 | ||||||||||||
| 2026-05-01T17:11:14Z | Oh, thanks. | website | clojurecivitas | 528764 | false | 11 | 592323688 | Daniel Slutsky | 138175 | 1777655474 | Hello, Babashka | |
| 2026-05-01T17:08:06Z | In dark mode the mermaid connection lines are not very visible. This might be fixed by adding the class “mermaid” (which is what happens here: https://clojurecivitas.org/scicloj/clay/mermaid.html) | ZulipElectron | clojurecivitas | 528764 | false | 911 | 592323242 | Timothy Pratley | 550430 | 1777655286 | Hello, Babashka | |
| “mermaid” and “js-plotly-plot” classes currently have their hue inverted for this purpose: | ||||||||||||
| https://github.com/ClojureCivitas/clojurecivitas.github.io/blob/68b5e81b4733aea601d0e5822eb88fa9b2adbdf6/site/styles.scss#L33 | ||||||||||||
| Alternatively this could be addressed by setting a background or using other properties to identify when hue should be inversed. In any case dark mode is just a minor issue but mentioning some details if you are curious. | ||||||||||||
| The tree of babashka icons looks really cool! | ||||||||||||
| How does kindly work? Is it using Clay or kindly-render or a custom implementation? | ||||||||||||
| Should we also hope for a Clojurqua? (Maybe Babashqua fills this need) | ||||||||||||
| Looking forward to hearing more details at the real-world-data meeting. | ||||||||||||
| 2026-05-01T16:51:39Z | amazing! | ZulipElectron | clojurecivitas | 528764 | false | 8 | 592320484 | Timothy Pratley | 550430 | 1777654299 | Hello, Babashka | |
| … | … | … | … | … | … | … | … | … | … | … | … | … |
| 2025-09-13T12:49:06Z | Despite the understandable habit of “Whereof one cannot speak, thereof one must be silent” when it comes to licenses, I think it is worth mentioning that the Eclipse license is not necessarily the best choice for Clojure libraries. For example, XTDB by Juxt is under a Mozilla license. I’d like to point to a 2021 Juxt-blogpost titled “Prefer the MIT License, The EPL is not a Sensible Default”: | website | clojurecivitas | 528764 | false | 1253 | 539269032 | Markus Agwin | 360348 | 1757767746 | licenses | |
quote | | | | | | | | | | | | | | It is common to see the following text at the bottom of READMEs in libraries from the Clojure ecosystem: | | | | | | | | | | | | | | | | | | | | | | | | | | | | Distributed under the Eclipse Public License, the same as Clojure. | | | | | | | | | | | | | | | | | | | | | | | | | | | | There is an implication worthy of deconstruction embedded in such a sentence. When we declare “we do X, the same as Clojure” what we’re really saying is “we use X because Clojure uses X.” This sort of thinking makes sense if our constraints mirror Clojure’s constraints. Unfortunately, it’s very unlikely that they do. | | | | | | | | | | | | | | |
||||||||||||
| Maybe it is worthwhile to contact Juxt for an updated blog post to be published on the Clojure Civitas website. After all, civitas means “citizens united by law” and it is certainly worthwhile to state that the Eclipse EPL is not the law that is meant to form Clojure Civitas. | ||||||||||||
| 2025-09-12T16:00:16Z | Hi thanks for all the guidance in the clojurescript thread. | ZulipElectron | clojurecivitas | 528764 | false | 767 | 539107122 | Timothy Pratley | 550430 | 1757692816 | A browser based REPL+chart with Scittle Kitchen | |
| To summarize, I think we are all interested in a Scittle driven chart based experience, but still unsure of the best way to render charts. | ||||||||||||
| 1. Tableplot is possibly the best option in Clojure, but currently is not available in Scittle. Some work (not Herculean) would be required to convert Tableplot source to cljc. | ||||||||||||
| 2. Vega is well established in this space. Possibly some work is necessary to find the nicest way to use it, and make the sizing appropriate. | ||||||||||||
| 3. thi.ng/geom has really compelling charts, but is a less widely used/known approach | ||||||||||||
| 4. emmy-viewers has entanglements that we wish to avoid. | ||||||||||||
| Please let me know if there are other alternatives, and or which if any seems the most promising future. | ||||||||||||
| 2025-09-12T09:58:38Z | @_Jarkko Saltiola|727865 said: | website | clojurecivitas | 528764 | false | 463 | 539025921 | Markus Agwin | 360348 | 1757671118 | licenses | |
quote | | | | | | | | | | | | | | there's no lawyers available to help | | | | | | | | | | | | | | |
||||||||||||
| I think this is the reason why people stopped to participate in discussions about license. Anything that is written in forums (including my writings) is not legally binding and ultimately in vain. Every person has to decide on his own which risk to take and which to avoid. | ||||||||||||
| 2025-09-12T06:16:10Z | I also discovered the EPL1.0 v.s. GPLv2+ incompatibility recently while I’ve been working around WordPress, particularly with a block editor (React based) Scittle integration https://codeberg.org/jasalt/wp-block-experiments. | website | clojurecivitas | 528764 | false | 1528 | 538989269 | Jarkko Saltiola | 727865 | 1757657770 | licenses | |
| While there are various workarounds, the “ecosystem” extension/plugin marketplace gives quite strict rules for non-GPL software: | ||||||||||||
| > All code, data, and images — anything stored in the plugin directory hosted on WordPress.org — must comply with the GPL or a GPL-Compatible license. Included third-party libraries, code, images, or otherwise, must be compatible. | ||||||||||||
| > Calling third party CDNs for reasons other than font inclusions; all non-service related JavaScript and CSS must be included locally | ||||||||||||
| This takes some fun out Clojure(Script) and similarly licensed dialects for me. All the time (2014-) I had thought EPL1.0 was just another Apache 2.0 -like license, or at least something I could do use freely around GPL licensed software. | ||||||||||||
| Had some discussion about it on Slack earlier, @**borkdude** would be happy to change Scittle and possibly his other works to BSD-3 but it is not known if it’s possible due to definition of derivative work being vaque in EPL1.0 and there’s no lawyers available to help. | ||||||||||||
| https://clojurians.slack.com/archives/C034FQN490E/p1751214935946939 | ||||||||||||
| https://clojurians.slack.com/archives/C07UQ678E/p1751280671744189 | ||||||||||||
| 2025-09-11T22:21:11Z | Things I know for sure about GPL: | 1757633171 | website | clojurecivitas | 528764 | true | 1341 | 538950637 | Markus Agwin | 360348 | 1757629271 | licenses |
| - On your laptop you can do, from a GPL standpoint, whatever you want | ||||||||||||
| - It never violates the GPL to publish source code | ||||||||||||
| - GPL pertains to published “object-code”. An example for an “object-code” is a binary compiled from C source-code. | ||||||||||||
| - It violates the GPL to publish a single “object-code” file which is generated from several source files which are partly GPL and partly Eclipse-EPL | ||||||||||||
| - Dynamically linking a GPL “object-code” to a non-GPL “Systems library” is permissable under GPL. An example for a “Systems library” is the window-system of an operating system. | ||||||||||||
| The above is what I know for sure. | ||||||||||||
| I do not know for sure, but I think it is fair to say: | ||||||||||||
A js-files compiled from a cljs-file (e.g. using shadow-cljs) is “object-code”. Thus it would clearly violate the GPL to publish a single file scittle.js that exposes both clojure.core (EPL) and Emmy (GPL). But we do not do that. |
||||||||||||
| My wishful thinking is: | ||||||||||||
Since, in scittle-kitchen, Emmy is a plugin, Emmy is dynamically linked to a “Systems library”. The “Systems library” is the file https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/scittle.js. |
||||||||||||
| But to be honest, I think my argument in my wishful thinking is a very weak one in favour of a positive answer to the question whether including Emmy in scittle-kitchen is or is not GPL complient. | ||||||||||||
| 2025-09-11T20:40:50Z | @_Daniel Slutsky|138175 changed the access permissions for this channel from Public to Web-public. | Internal | clojurecivitas | 528764 | false | 139 | 538939857 | Notification Bot | 100006 | 1757623250 | channel events | |
| 2025-09-11T19:54:57Z | Where should we discuss ClojureCivitas? | 1757622002 | ZulipElectron | clojurecivitas | 528764 | true | 197 | 538933920 | Timothy Pratley | 550430 | 1757620497 | Maybe this channel should not exist |
| One option is to enable discussion on the site itself: | ||||||||||||
| https://quarto.org/docs/output-formats/html-basics.html#commenting | ||||||||||||
| or in #announce>ClojureCivitas | ||||||||||||
| 2025-09-11T19:50:44Z | @**Markus Agwin** @**Daniel Slutsky** suggesting we discuss here instead of in clojurescript as we might be getting a bit off-topic for that channel :shrug: | ZulipElectron | clojurecivitas | 528764 | false | 156 | 538933408 | Timothy Pratley | 550430 | 1757620244 | A browser based REPL+chart with Scittle Kitchen | |
| 2025-09-11T19:49:34Z | #clojurescript>Looking for early feedback on Scittle Kitchen@538823252 | ZulipElectron | clojurecivitas | 528764 | false | 74 | 538933272 | Timothy Pratley | 550430 | 1757620174 | A browser based REPL+chart with Scittle Kitchen | |
| 2025-09-11T19:47:11Z | #clojurescript>Looking for early feedback on Scittle Kitchen@538924975 | ZulipElectron | clojurecivitas | 528764 | false | 201 | 538933023 | Timothy Pratley | 550430 | 1757620031 | licenses | |
| Civitas accepts many dependencies and is for writing about them. It’s not clear what (if any) license implications that has. | ||||||||||||
| 2025-09-11T19:43:40Z | Public channel created by @_Timothy Pratley|550430. Description: | Internal | clojurecivitas | 528764 | false | 110 | 538932511 | Notification Bot | 100006 | 1757619820 | channel events | |
quote | | | | | | | | | | | | | | *No description.* | | | | | | | | | | | | | | |
(tc/row-count timeline)273The dataset’s columns:
(tc/column-names timeline)(:instant
:content
:last-edit-ts
:client
:channel
:stream-id
:edited
:content-length
:id
:sender
:sender-id
:timestamp
:subject)Next steps
The rest of this book is one chapter per namespace:
The REST client — what
client/whoamidoes internally, plus the four endpoints the library wraps.Pulling and caching channels — the cache model behind
pull/pull-channels!, plus options like:refreshfor keeping a corpus up to date.Tablecloth views —
views/messages-timeline,views/reactions-long,views/edits-long, andviews/topic-links-long.Anonymized views — parallel views with sender names and topic strings replaced by stable hash keys, message content dropped — for sharing datasets without leaking identities or text.
Narrative — date columns, channel lifecycles, and newcomer tracking.
Graph views — co-membership and co-presence graphs, community detection, and rendering.
API Reference — every public function in one chapter.