A modern, type-safe Kotlin client for the Notion API with comprehensive DSL support and coroutine-based operations.
⚠️ AI-Assisted Development Notice This library was developed with significant assistance from Claude Code (AI). While it includes comprehensive testing (860+ unit tests) and validation against official Notion API samples, please be aware of potential issues:
- Documentation examples may not perfectly match implementation
- Edge cases may exist that weren't covered in testing
- Some API patterns may have inconsistencies
Please report any issues you encounter! Your feedback is invaluable for improving the library. See the Development Context section for full transparency about the development process.
- 🆕 Latest API Support - Built for Notion API version 2026-03-11 with full support for data sources, search, and all current features (currently the only Kotlin client supporting this API version)
- 🛡️ Type-Safe DSLs - Intuitive builder patterns for pages, databases, blocks, and queries with compile-time safety
- ⚡ Kotlin-First - Leverages coroutines for non-blocking I/O, null-safety, and functional programming patterns
- ✅ Extensively Tested - Validated against official Notion API sample responses with comprehensive test coverage
// Gradle (Kotlin DSL)
dependencies {
implementation("it.saabel:kotlin-notion-client:0.5.0")
}<!-- Maven -->
<dependency>
<groupId>it.saabel</groupId>
<artifactId>kotlin-notion-client</artifactId>
<version>0.5.0</version>
</dependency>- Kotlin 2.3.21 or higher
- JVM target 21+
import it.saabel.kotlinnotionclient.NotionClient
// Initialize the client (constructor - recommended)
val notion = NotionClient("your-notion-api-token")
// Alternative: factory method (also supported)
// val notion = NotionClient.create("your-notion-api-token")
// Retrieve a page
val page = notion.pages.retrieve("page-id")
// Create a page in a data source
val newPage = notion.pages.create {
parent { dataSourceId("data-source-id") }
properties {
title("Name") { text("My Project") }
select("Status") { name("In Progress") }
}
}
// Query a data source (table)
val results = notion.dataSources.query("data-source-id") {
filter {
property("Status") {
select { equals("In Progress") }
}
}
}You can create a NotionClient instance using either pattern:
// 1. Direct constructor (idiomatic Kotlin - recommended)
val client = NotionClient("your-api-token")
// 2. Factory method (also fully supported)
val client = NotionClient.create("your-api-token")
// With custom configuration
val client = NotionClient(
NotionConfig(
apiToken = "your-api-token",
logLevel = LogLevel.INFO,
enableRateLimit = true
)
)Both patterns are fully supported - use whichever feels more natural to you.
Every request the client sends flows through a single rate-limit pipeline that does two things automatically:
- Proactive throttling — a continuous-refill token bucket paces outbound
requests so you stay under Notion's documented sustained ceiling (≈3 req/s)
instead of firing a burst that earns a wave of
429s. - Reactive retry —
429responses (honouringRetry-After), transient5xx(502/503/504), and network errors are retried with exponential backoff and jitter. Other4xx, plain500, and successes are returned immediately.
Tune it through NotionConfig.rateLimitConfig:
val client = NotionClient(
NotionConfig(
apiToken = "your-api-token",
rateLimitConfig = RateLimitConfig(
sustainedRate = 3.0, // tokens (requests) refilled per second
burstCapacity = 20, // requests allowed to fire immediately
maxRetries = 3, // retries AFTER the first attempt (see note)
retryBaseDelay = 1.seconds, // first retry waits ~this long
retryMaxDelay = 30.seconds, // cap on any single backoff delay
jitterFactor = 0.1, // ±10% randomness to avoid thundering herds
),
),
)| Field | Default | What it controls |
|---|---|---|
sustainedRate |
3.0 |
Steady-state requests per second. Notion's sustained ceiling — lower for politeness; raising it risks 429s. |
burstCapacity |
20 |
How many requests can go out back-to-back before pacing kicks in. |
maxRetries |
3 |
Retries after the initial attempt. |
retryBaseDelay |
1s |
Base of the exponential backoff schedule. |
retryMaxDelay |
30s |
Upper bound on a single backoff wait. |
jitterFactor |
0.1 |
Randomness added to each delay. |
Off-by-one note:
maxRetriescounts retries, not total attempts.maxRetries = 3permits up to 4 HTTP calls (1 initial + 3 retries);0disables retrying entirely.
Tuning for heavy concurrency. If you fan out many requests at once (e.g.
bulk-creating pages or hydrating a large database), raise burstCapacity
rather than sustainedRate. A larger bucket lets the initial burst drain
immediately, after which everything is paced at sustainedRate; sustainedRate
itself should stay pinned to what Notion allows. The bucket is scoped per
NotionClient, so independent clients throttle independently.
To opt out entirely (e.g. when you front the client with your own limiter), set
enableRateLimit = false.
Important: The 2025-09-03 API introduced a fundamental change to how databases work:
- Database = Container that holds one or more data sources
- Data Source = The actual table with properties and rows (pages)
Most operations you'd expect to do on a "database" (like querying, adding pages) actually work on data sources:
// ❌ In older APIs: notion.databases.query("database-id")
// ✅ In 2025-09-03+: notion.dataSources.query("data-source-id")The DatabasesApi is for container-level operations (create database, update title/icon/cover). The DataSourcesApi is for data operations (query, create data source, update schema).
See docs/databases.md and docs/data-sources.md for details.
| API Category | Status | Documentation |
|---|---|---|
| Pages | ✅ Complete | docs/pages.md |
| Databases | ✅ Complete | docs/databases.md |
| Blocks | ✅ Complete | docs/blocks.md |
| Data Sources | ✅ Complete | docs/data-sources.md |
| Search | ✅ Complete | docs/search.md |
| Users | ✅ Complete | docs/users.md |
| Comments | ✅ Complete | docs/comments.md |
| File Uploads | ✅ Complete | docs/file-uploads.md |
| Markdown | ✅ Complete | docs/markdown-api.md |
| Custom Emojis | ✅ Complete | docs/custom-emojis.md |
| Views | ✅ Complete | docs/views-api.md |
- All CRUD operations for pages, databases, and blocks
- Page management - Move pages between parents, lock/unlock pages, control page position
- Templates API - List data source templates and create pages from templates
- Advanced query DSL with complex filters, sorting, pagination, and timestamp filters
- Rich text DSL for formatted content with mentions, equations, and links
- 33+ block types including tables, callouts, code blocks, embeds,
heading_4,tab, andmeeting_notes - Markdown Content API — retrieve, create, and update page content as Markdown
- Views API — full CRUD for database views with typed
ViewConfiguration - Native icons — Notion's built-in icon library with 10 color options
- Custom emoji listing — enumerate workspace custom emojis
- Property types - Full support for the most important data source property types, with more coming up
- Kotlin datetime types - Native support for
kotlinx-datetimetypes (LocalDate,LocalDateTime,Instant) with explicit timezone handling - File operations - Upload and manage files/images
- Comprehensive error handling with detailed error types
- Quick Start Guide - Get up and running in 5 minutes
- Kotlin Notebooks - Interactive Jupyter notebooks with 55+ examples (recommended for learning)
- API Documentation - Detailed guides for each API category
- Rich Text DSL - Working with formatted text
- Error Handling - Understanding and handling errors
- Testing - Testing your Notion integrations
The Kotlin Notebooks are the best way to learn the library:
- Getting Started - Authentication and basic operations
- Reading Databases - Querying with filters and sorting
- Creating Pages - Pages, properties, icons, and covers
- Working with Blocks - All block types and operations
- Rich Text DSL - Formatting, colors, links, dates, equations
- Advanced Queries - Complex filtering, AND/OR logic, pagination
- File Uploads - Uploading files, external imports, media blocks
- What's New in v0.4.0 — Tour of all breaking changes and new features added in v0.4.0
All notebooks use v0.5.0 and can be run in IntelliJ IDEA (with the Kotlin Notebook plugin) or Jupyter (with kotlin-jupyter-kernel). Run each cell in order; set the required environment variables before starting. Note that notebook output cells reflect the authoring-time version and have not all been re-run for v0.5.0.
# Clone the repository
git clone https://github.com/jsaabel/kotlin-notion-client.git
cd kotlin-notion-client
# Build the project
./gradlew build
# Run all tests (unit tests run, integration tests skip without env vars)
./gradlew test
# Run integration tests (requires environment variables)
export NOTION_RUN_INTEGRATION_TESTS=true
export NOTION_API_TOKEN="secret_..."
export NOTION_TEST_PAGE_ID="page-id"
./gradlew test
# Run specific integration tests (recommended)
./gradlew test --tests "*SearchIntegrationTest"Notes:
- Integration tests are controlled via environment variables. Without
NOTION_RUN_INTEGRATION_TESTS=true, they are automatically skipped. ⚠️ Avoid running all integration tests at once - they perform many real API operations on your workspace. Run specific test classes instead.
This project was developed using Claude Code (Anthropic's CLI for Claude) as an exploration of AI-assisted software development. The goal was to understand how human expertise and AI capabilities can collaborate on production-quality code.
Key aspects:
- Iterative, test-driven development with AI assistance
- Extensive use of official Notion API samples for validation
- Transparent development process documented in journal/
The development journals are intentionally kept in the repository for transparency and educational value for others exploring LLM-assisted development workflows.
This library covers virtually all aspects of the Notion API (2026-03-11 version). It has been validated against official API samples and includes both unit tests and integration tests. However, it has not yet been used extensively in production environments.
Before using in production:
- Review the test coverage for your specific use cases
- Test thoroughly with your Notion workspace
- Be aware that the API version support is fixed to 2026-03-11
- Date/time properties with timezones: While the library provides comprehensive support for
kotlinx-datetimetypes with explicit timezone handling (including timezone-aware conversions viatoLocalDateTime(timeZone)), this area may benefit from additional real-world validation, particularly around timezone edge cases and complex datetime scenarios
Contributions are welcome!
Areas where contributions are especially valued:
- Real-world usage feedback and bug reports
- Additional test coverage
- Documentation improvements
- Performance optimizations
This project is licensed under the MIT License.
- Notion for their comprehensive API and excellent documentation
- Anthropic for Claude and Claude Code
- JetBrains for Kotlin and the fantastic language ecosystem
- The Kotlin community for patterns and best practices
- Previous Kotlin implementations (klibnotion and notion-sdk-kotlin) for pioneering Kotlin-based Notion API clients and serving as valuable references during development
This is not an official Notion product. Notion is a trademark of Notion Labs, Inc.