A simple and easy to use client for the Notion API
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive error handling with specific error types and codes for different failure scenarios.
The Notion SDK provides specific error classes for different types of failures.
/**
* Union type of all possible errors from the Notion client
*/
type NotionClientError =
| APIResponseError
| UnknownHTTPResponseError
| RequestTimeoutError;
/**
* Check if an error is a Notion client error
* @param error - Error to check
* @returns True if error is from Notion client
*/
function isNotionClientError(error: unknown): error is NotionClientError;Errors returned from the Notion API with specific error codes.
/**
* Error thrown when API returns an error response
*/
class APIResponseError extends Error {
/** Error code from the API */
code: APIErrorCode;
/** HTTP status code */
status: number;
/** Additional error details */
headers: Record<string, string>;
/** Raw response body */
body: string;
}
/**
* Error codes returned by the Notion API
*/
enum APIErrorCode {
Unauthorized = "unauthorized";
RestrictedResource = "restricted_resource";
ObjectNotFound = "object_not_found";
RateLimited = "rate_limited";
InvalidJSON = "invalid_json";
InvalidRequestURL = "invalid_request_url";
InvalidRequest = "invalid_request";
ValidationError = "validation_error";
ConflictError = "conflict_error";
InternalServerError = "internal_server_error";
ServiceUnavailable = "service_unavailable";
}Usage Examples:
import { Client, APIErrorCode, isNotionClientError } from "@notionhq/client";
const notion = new Client({ auth: process.env.NOTION_TOKEN });
try {
const page = await notion.pages.retrieve({
page_id: "invalid-page-id",
});
} catch (error) {
if (isNotionClientError(error)) {
console.log(`Notion error: ${error.code}`);
if (error.code === APIErrorCode.ObjectNotFound) {
console.log("Page not found");
} else if (error.code === APIErrorCode.Unauthorized) {
console.log("Invalid token or insufficient permissions");
} else if (error.code === APIErrorCode.RateLimited) {
console.log("Rate limited - retry after delay");
}
} else {
console.log("Unknown error:", error);
}
}Errors generated by the client itself, not the API.
/**
* Error codes for client-side errors
*/
enum ClientErrorCode {
RequestTimeout = "notionhq_client_request_timeout";
ResponseError = "notionhq_client_response_error";
}
/**
* Error thrown when request times out
*/
class RequestTimeoutError extends Error {
code: ClientErrorCode.RequestTimeout;
}
/**
* Error thrown for unknown HTTP response errors
*/
class UnknownHTTPResponseError extends Error {
code: ClientErrorCode.ResponseError;
/** HTTP status code */
status: number;
/** Response body */
body: string;
}Usage Examples:
import { Client, ClientErrorCode } from "@notionhq/client";
const notion = new Client({
auth: process.env.NOTION_TOKEN,
timeoutMs: 5000, // 5 second timeout
});
try {
const page = await notion.pages.retrieve({
page_id: "page-id",
});
} catch (error) {
if (isNotionClientError(error)) {
if (error.code === ClientErrorCode.RequestTimeout) {
console.log("Request timed out - try again");
} else if (error.code === ClientErrorCode.ResponseError) {
console.log(`HTTP error ${error.status}: ${error.body}`);
}
}
}/**
* Union of all error codes (API + Client)
*/
type NotionErrorCode = APIErrorCode | ClientErrorCode;async function safePage Retrieval(pageId: string) {
try {
const page = await notion.pages.retrieve({ page_id: pageId });
return { success: true, page };
} catch (error) {
if (isNotionClientError(error)) {
return {
success: false,
error: error.code,
message: error.message
};
}
return {
success: false,
error: "unknown",
message: "Unknown error occurred"
};
}
}async function retryWithBackoff<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (isNotionClientError(error)) {
// Don't retry certain errors
if (error.code === APIErrorCode.Unauthorized ||
error.code === APIErrorCode.ObjectNotFound ||
error.code === APIErrorCode.InvalidRequest) {
throw error;
}
// Retry rate limited or server errors
if (error.code === APIErrorCode.RateLimited ||
error.code === APIErrorCode.InternalServerError ||
error.code === APIErrorCode.ServiceUnavailable ||
error.code === ClientErrorCode.RequestTimeout) {
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
throw error;
}
}
throw lastError!;
}
// Usage
const page = await retryWithBackoff(() =>
notion.pages.retrieve({ page_id: "page-id" })
);function handleNotionError(error: unknown) {
if (!isNotionClientError(error)) {
console.log("Non-Notion error:", error);
return;
}
console.log(`Error Code: ${error.code}`);
console.log(`Error Message: ${error.message}`);
if (error instanceof APIResponseError) {
console.log(`HTTP Status: ${error.status}`);
console.log(`Response Headers:`, error.headers);
console.log(`Response Body:`, error.body);
}
if (error instanceof UnknownHTTPResponseError) {
console.log(`HTTP Status: ${error.status}`);
console.log(`Response Body:`, error.body);
}
// Handle specific error codes
switch (error.code) {
case APIErrorCode.Unauthorized:
console.log("Check your API token and permissions");
break;
case APIErrorCode.RateLimited:
console.log("Slow down requests to avoid rate limiting");
break;
case APIErrorCode.ObjectNotFound:
console.log("The requested resource was not found");
break;
case APIErrorCode.ValidationError:
console.log("Request data failed validation");
break;
case ClientErrorCode.RequestTimeout:
console.log("Request timed out - consider increasing timeout");
break;
}
}async function getPageOrFallback(pageId: string) {
try {
return await notion.pages.retrieve({ page_id: pageId });
} catch (error) {
if (isNotionClientError(error)) {
if (error.code === APIErrorCode.ObjectNotFound) {
// Return a default page structure
return {
object: "page" as const,
id: pageId,
properties: {},
// ... other default properties
};
}
if (error.code === APIErrorCode.Unauthorized) {
throw new Error("Authentication required");
}
}
// Re-throw other errors
throw error;
}
}enum APIErrorCode {
Unauthorized = "unauthorized";
RestrictedResource = "restricted_resource";
ObjectNotFound = "object_not_found";
RateLimited = "rate_limited";
InvalidJSON = "invalid_json";
InvalidRequestURL = "invalid_request_url";
InvalidRequest = "invalid_request";
ValidationError = "validation_error";
ConflictError = "conflict_error";
InternalServerError = "internal_server_error";
ServiceUnavailable = "service_unavailable";
}
enum ClientErrorCode {
RequestTimeout = "notionhq_client_request_timeout";
ResponseError = "notionhq_client_response_error";
}
type NotionErrorCode = APIErrorCode | ClientErrorCode;
type NotionClientError = APIResponseError | UnknownHTTPResponseError | RequestTimeoutError;
class APIResponseError extends Error {
code: APIErrorCode;
status: number;
headers: Record<string, string>;
body: string;
}
class UnknownHTTPResponseError extends Error {
code: ClientErrorCode.ResponseError;
status: number;
body: string;
}
class RequestTimeoutError extends Error {
code: ClientErrorCode.RequestTimeout;
}
function isNotionClientError(error: unknown): error is NotionClientError;Install with Tessl CLI
npx tessl i tessl/npm-notionhq--client