Skip to content

jsaabel/kotlin-notion-client

Repository files navigation

Kotlin Notion Client

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.

Why This Client?

  • 🆕 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

Installation

Maven Central

// 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>

Requirements

  • Kotlin 2.3.21 or higher
  • JVM target 21+

Quick Start

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") }
        }
    }
}

Client Initialization

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.

Rate Limiting & Throttling

Every request the client sends flows through a single rate-limit pipeline that does two things automatically:

  1. 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.
  2. Reactive retry429 responses (honouring Retry-After), transient 5xx (502 / 503 / 504), and network errors are retried with exponential backoff and jitter. Other 4xx, plain 500, 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: maxRetries counts retries, not total attempts. maxRetries = 3 permits up to 4 HTTP calls (1 initial + 3 retries); 0 disables 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.

Understanding Databases vs. Data Sources

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 Coverage

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

Feature Highlights

  • 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, and meeting_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-datetime types (LocalDate, LocalDateTime, Instant) with explicit timezone handling
  • File operations - Upload and manage files/images
  • Comprehensive error handling with detailed error types

Documentation

Learning Resources

The Kotlin Notebooks are the best way to learn the library:

  1. Getting Started - Authentication and basic operations
  2. Reading Databases - Querying with filters and sorting
  3. Creating Pages - Pages, properties, icons, and covers
  4. Working with Blocks - All block types and operations
  5. Rich Text DSL - Formatting, colors, links, dates, equations
  6. Advanced Queries - Complex filtering, AND/OR logic, pagination
  7. File Uploads - Uploading files, external imports, media blocks
  8. 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.

Building from Source

# 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.

Development Context

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.

Project Status

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-datetime types with explicit timezone handling (including timezone-aware conversions via toLocalDateTime(timeZone)), this area may benefit from additional real-world validation, particularly around timezone edge cases and complex datetime scenarios

Contributing

Contributions are welcome!

Areas where contributions are especially valued:

  • Real-world usage feedback and bug reports
  • Additional test coverage
  • Documentation improvements
  • Performance optimizations

License

This project is licensed under the MIT License.

Acknowledgments

  • 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.

About

A modern, type-safe Kotlin client for the Notion API with comprehensive DSL support and coroutine-based operations. Adapted to work with the latest API version.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Contributors