|
1 | 1 | # Users API |
2 | 2 |
|
3 | | -> **⚠️ WORK IN PROGRESS**: This documentation is being actively developed and may be incomplete or subject to change. |
4 | | -
|
5 | 3 | ## Overview |
6 | 4 |
|
7 | | -The Users API allows you to retrieve information about users and bots in your Notion workspace. |
| 5 | +The Users API allows you to retrieve information about users and bots in your Notion workspace. The API provides three main operations: |
| 6 | + |
| 7 | +1. **Get Current Bot User** - Always available, returns information about your integration |
| 8 | +2. **Retrieve User by ID** - Requires user information capabilities |
| 9 | +3. **List All Users** - Requires user information capabilities, returns paginated list |
8 | 10 |
|
9 | | -**Official Documentation**: [Notion Users API](https://developers.notion.com/reference/get-user) |
| 11 | +**Official Documentation**: [Notion Users API](https://developers.notion.com/reference/get-users) |
10 | 12 |
|
11 | 13 | ## Available Operations |
12 | 14 |
|
13 | 15 | ```kotlin |
14 | | -// Retrieve a user by ID |
| 16 | +// Get current bot user (always available) |
| 17 | +suspend fun getCurrentUser(): User |
| 18 | + |
| 19 | +// Retrieve a specific user by ID (requires user information capabilities) |
15 | 20 | suspend fun retrieve(userId: String): User |
16 | 21 |
|
17 | | -// List all users |
18 | | -suspend fun list(): PaginatedList<User> |
| 22 | +// List all users in workspace (requires user information capabilities) |
| 23 | +suspend fun list(startCursor: String? = null, pageSize: Int? = null): UserList |
| 24 | +``` |
19 | 25 |
|
20 | | -// Get current bot user |
21 | | -suspend fun getCurrentUser(): User |
| 26 | +## User Model |
| 27 | + |
| 28 | +```kotlin |
| 29 | +data class User( |
| 30 | + val objectType: String, // Always "user" |
| 31 | + val id: String, // UUID of the user |
| 32 | + val name: String?, // Display name |
| 33 | + val avatarUrl: String?, // Avatar image URL |
| 34 | + val type: UserType?, // PERSON or BOT |
| 35 | + val person: PersonInfo?, // Present for person users |
| 36 | + val bot: BotInfo? // Present for bot users |
| 37 | +) |
| 38 | + |
| 39 | +enum class UserType { |
| 40 | + PERSON, // A human user |
| 41 | + BOT // A bot/integration |
| 42 | +} |
22 | 43 | ``` |
23 | 44 |
|
24 | 45 | ## Examples |
25 | 46 |
|
26 | | -_TODO: Add comprehensive examples_ |
| 47 | +### Basic: Get Current Bot User |
| 48 | + |
| 49 | +Always available, doesn't require special capabilities: |
| 50 | + |
| 51 | +```kotlin |
| 52 | +val notion = NotionClient.create(NotionConfig(apiToken = "your-secret-token")) |
| 53 | + |
| 54 | +val botUser = notion.users.getCurrentUser() |
| 55 | +println("Bot name: ${botUser.name}") |
| 56 | +println("Bot ID: ${botUser.id}") |
| 57 | +println("Type: ${botUser.type}") // Will be UserType.BOT |
| 58 | +``` |
| 59 | + |
| 60 | +### Retrieve a Specific User by ID |
27 | 61 |
|
28 | | -### Get Current User |
| 62 | +Requires **user information capabilities**: |
29 | 63 |
|
30 | 64 | ```kotlin |
31 | | -val user = notion.users.getCurrentUser() |
32 | | -println("Bot name: ${user.name}") |
33 | | -println("User ID: ${user.id}") |
| 65 | +try { |
| 66 | + val user = notion.users.retrieve("d40e767c-d7af-4b18-a86d-55c61f1e39a4") |
| 67 | + |
| 68 | + when (user.type) { |
| 69 | + UserType.PERSON -> { |
| 70 | + println("Person: ${user.name}") |
| 71 | + user.person?.email?.let { email -> |
| 72 | + println("Email: $email") |
| 73 | + } |
| 74 | + } |
| 75 | + UserType.BOT -> { |
| 76 | + println("Bot: ${user.name}") |
| 77 | + } |
| 78 | + else -> println("Unknown user type") |
| 79 | + } |
| 80 | +} catch (e: NotionException.ApiError) { |
| 81 | + if (e.status == 403) { |
| 82 | + println("Integration lacks user information capabilities") |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +### List All Users in Workspace |
| 88 | + |
| 89 | +Requires **user information capabilities**: |
| 90 | + |
| 91 | +```kotlin |
| 92 | +try { |
| 93 | + val userList = notion.users.list() |
| 94 | + |
| 95 | + userList.results.forEach { user -> |
| 96 | + println("${user.name} (${user.type})") |
| 97 | + } |
| 98 | + |
| 99 | + if (userList.hasMore) { |
| 100 | + println("More users available, cursor: ${userList.nextCursor}") |
| 101 | + } |
| 102 | +} catch (e: NotionException.ApiError) { |
| 103 | + if (e.status == 403) { |
| 104 | + println("Integration lacks user information capabilities") |
| 105 | + } |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +### List with Pagination |
| 110 | + |
| 111 | +Control page size and navigate through results: |
| 112 | + |
| 113 | +```kotlin |
| 114 | +// Get first page |
| 115 | +val firstPage = notion.users.list(pageSize = 50) |
| 116 | +firstPage.results.forEach { user -> |
| 117 | + println(user.name) |
| 118 | +} |
| 119 | + |
| 120 | +// Get next page if available |
| 121 | +if (firstPage.hasMore && firstPage.nextCursor != null) { |
| 122 | + val secondPage = notion.users.list( |
| 123 | + startCursor = firstPage.nextCursor, |
| 124 | + pageSize = 50 |
| 125 | + ) |
| 126 | +} |
34 | 127 | ``` |
35 | 128 |
|
36 | | -### Retrieve a Specific User |
| 129 | +### Working with Person vs Bot Users |
37 | 130 |
|
38 | 131 | ```kotlin |
39 | | -val user = notion.users.retrieve("user-id") |
40 | | -println("Name: ${user.name}") |
41 | | -println("Type: ${user.type}") |
| 132 | +val user = notion.users.retrieve(userId) |
| 133 | + |
| 134 | +when (user.type) { |
| 135 | + UserType.PERSON -> { |
| 136 | + println("This is a person user") |
| 137 | + val email = user.person?.email ?: "Email not available" |
| 138 | + println("Email: $email") |
| 139 | + } |
| 140 | + UserType.BOT -> { |
| 141 | + println("This is a bot integration") |
| 142 | + user.bot?.owner?.let { owner -> |
| 143 | + println("Owner type: ${owner.type}") |
| 144 | + } |
| 145 | + } |
| 146 | + null -> println("User type not specified") |
| 147 | +} |
42 | 148 | ``` |
43 | 149 |
|
44 | | -### List All Users |
| 150 | +### Validate API Token at Startup |
45 | 151 |
|
46 | 152 | ```kotlin |
47 | | -val users = notion.users.list() |
48 | | -users.results.forEach { user -> |
49 | | - println("${user.name} (${user.type})") |
| 153 | +suspend fun initializeNotionClient(token: String): NotionClient { |
| 154 | + val client = NotionClient.create(NotionConfig(apiToken = token)) |
| 155 | + |
| 156 | + try { |
| 157 | + val botUser = client.users.getCurrentUser() |
| 158 | + println("✓ Authenticated as: ${botUser.name}") |
| 159 | + return client |
| 160 | + } catch (e: NotionException.AuthenticationError) { |
| 161 | + throw IllegalStateException("Invalid API token", e) |
| 162 | + } |
50 | 163 | } |
51 | 164 | ``` |
52 | 165 |
|
53 | | -## User Types |
| 166 | +## Integration Capabilities |
| 167 | + |
| 168 | +The Users API has different requirements for different operations: |
54 | 169 |
|
55 | | -Users can be: |
56 | | -- `person` - A human user |
57 | | -- `bot` - A bot/integration |
| 170 | +| Operation | Required Capability | Error if Missing | |
| 171 | +|-----------|---------------------|------------------| |
| 172 | +| `getCurrentUser()` | None | N/A | |
| 173 | +| `retrieve(userId)` | User information | 403 Forbidden | |
| 174 | +| `list()` | User information | 403 Forbidden | |
| 175 | + |
| 176 | +To add user information capabilities to your integration: |
| 177 | +1. Go to your integration settings in Notion |
| 178 | +2. Navigate to the "Capabilities" section |
| 179 | +3. Enable "Read user information including email addresses" |
58 | 180 |
|
59 | 181 | ## Common Patterns |
60 | 182 |
|
61 | | -_TODO: Add tips, gotchas, best practices_ |
| 183 | +### Graceful Handling of Missing Capabilities |
| 184 | + |
| 185 | +```kotlin |
| 186 | +suspend fun getUserInfo(client: NotionClient, userId: String): User? { |
| 187 | + return try { |
| 188 | + client.users.retrieve(userId) |
| 189 | + } catch (e: NotionException.ApiError) { |
| 190 | + when (e.status) { |
| 191 | + 403 -> { |
| 192 | + println("Integration lacks user capabilities") |
| 193 | + null |
| 194 | + } |
| 195 | + 404 -> { |
| 196 | + println("User not found") |
| 197 | + null |
| 198 | + } |
| 199 | + else -> throw e |
| 200 | + } |
| 201 | + } |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +### Iterate Through All Users |
| 206 | + |
| 207 | +```kotlin |
| 208 | +suspend fun getAllUsers(client: NotionClient): List<User> { |
| 209 | + val allUsers = mutableListOf<User>() |
| 210 | + var cursor: String? = null |
| 211 | + |
| 212 | + do { |
| 213 | + val page = client.users.list(startCursor = cursor, pageSize = 100) |
| 214 | + allUsers.addAll(page.results) |
| 215 | + cursor = page.nextCursor |
| 216 | + } while (page.hasMore) |
| 217 | + |
| 218 | + return allUsers |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +## Limitations |
| 223 | + |
| 224 | +- **No email/name filtering**: The API doesn't support searching users by email or name |
| 225 | +- **Guests not included**: `list()` doesn't return guest users |
| 226 | +- **Read-only**: You cannot create or modify users through the API |
| 227 | +- **Email access restricted**: Email addresses only visible with specific capabilities |
| 228 | + |
| 229 | +## Tips and Best Practices |
| 230 | + |
| 231 | +### ✅ Best Practices |
| 232 | + |
| 233 | +1. **Call getCurrentUser() at startup** to validate your API token |
| 234 | +2. **Cache bot user info** since it doesn't change during runtime |
| 235 | +3. **Handle null properties** - name, avatarUrl, and person/bot fields are optional |
| 236 | +4. **Gracefully handle 403 errors** for retrieve() and list() |
| 237 | +5. **Use page size of 100** for efficient pagination (maximum allowed) |
| 238 | + |
| 239 | +### ⚠️ Common Gotchas |
| 240 | + |
| 241 | +1. **getCurrentUser() only returns your bot** - never returns person users |
| 242 | +2. **Limited user data** - API has strict privacy controls |
| 243 | +3. **403 means missing capabilities** - not an authentication error |
| 244 | +4. **Guests aren't in list()** - only full workspace members and bots |
| 245 | +5. **Email may be null** - even with capabilities, not all users have emails |
| 246 | + |
| 247 | +## Error Handling |
| 248 | + |
| 249 | +```kotlin |
| 250 | +try { |
| 251 | + val user = notion.users.retrieve(userId) |
| 252 | + // ... use user |
| 253 | +} catch (e: NotionException.AuthenticationError) { |
| 254 | + println("Authentication failed: Check your API token") |
| 255 | +} catch (e: NotionException.ApiError) { |
| 256 | + when (e.status) { |
| 257 | + 403 -> println("Missing user information capabilities") |
| 258 | + 404 -> println("User not found") |
| 259 | + 429 -> println("Rate limited - wait and retry") |
| 260 | + else -> println("API error: ${e.details}") |
| 261 | + } |
| 262 | +} catch (e: NotionException.NetworkError) { |
| 263 | + println("Network error: ${e.cause?.message}") |
| 264 | +} catch (e: IllegalArgumentException) { |
| 265 | + println("Invalid parameter: ${e.message}") |
| 266 | +} |
| 267 | +``` |
| 268 | + |
| 269 | +See **[Error Handling](error-handling.md)** for comprehensive error handling patterns. |
| 270 | + |
| 271 | +## Testing |
| 272 | + |
| 273 | +- **Unit tests**: `src/test/kotlin/unit/api/UsersApiTest.kt` |
| 274 | +- **Integration tests**: `src/test/kotlin/integration/UsersIntegrationTest.kt` |
| 275 | +- **Examples**: `src/test/kotlin/examples/UsersExamples.kt` |
| 276 | + |
| 277 | +See **[Testing](testing.md)** for more information. |
62 | 278 |
|
63 | 279 | ## Related APIs |
64 | 280 |
|
65 | | -- **[Pages](pages.md)** - Users can be mentioned in pages |
66 | | -- **[Comments](comments.md)** - Users can create comments |
| 281 | +- **[Pages](pages.md)** - Users appear in `created_by` and `last_edited_by` fields |
| 282 | +- **[Databases](databases.md)** - Users appear in `created_by` and `last_edited_by` fields |
| 283 | +- **[Blocks](blocks.md)** - Users appear in `created_by` and `last_edited_by` fields |
| 284 | +- **[Comments](comments.md)** - Users create and own comments |
| 285 | +- **[Error Handling](error-handling.md)** - Handling API errors and validation |
0 commit comments