Skip to content

Commit 2b97c20

Browse files
jsaabelclaude
andcommitted
feat: implement complete Users API with retrieve and list methods
Adds full Users API support with three operations: getCurrentUser() (existing), retrieve(userId), and list() with pagination. Includes comprehensive tests, documentation, and examples validated against live API. Key additions: - retrieve(userId): Get specific user by ID (requires user capabilities) - list(startCursor, pageSize): List all workspace users with pagination - PersonInfo and UserList models for person users and paginated responses - Made BotInfo.owner optional to handle API edge cases - 15 unit tests with official API samples - Integration tests with UUID normalization - 9 example tests matching documentation All 472 unit tests passing. Integration tests handle 403 gracefully when missing user information capabilities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent fa34512 commit 2b97c20

29 files changed

+1892
-38
lines changed

docs/users.md

Lines changed: 248 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,285 @@
11
# Users API
22

3-
> **⚠️ WORK IN PROGRESS**: This documentation is being actively developed and may be incomplete or subject to change.
4-
53
## Overview
64

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
810

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)
1012

1113
## Available Operations
1214

1315
```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)
1520
suspend fun retrieve(userId: String): User
1621

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+
```
1925

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+
}
2243
```
2344

2445
## Examples
2546

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
2761

28-
### Get Current User
62+
Requires **user information capabilities**:
2963

3064
```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+
}
34127
```
35128

36-
### Retrieve a Specific User
129+
### Working with Person vs Bot Users
37130

38131
```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+
}
42148
```
43149

44-
### List All Users
150+
### Validate API Token at Startup
45151

46152
```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+
}
50163
}
51164
```
52165

53-
## User Types
166+
## Integration Capabilities
167+
168+
The Users API has different requirements for different operations:
54169

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"
58180

59181
## Common Patterns
60182

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

63279
## Related APIs
64280

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

Comments
 (0)