or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

blocks-users.mdcollections.mdcore-api.mdfiles-urls.mdindex.mdsearch.md

files-urls.mddocs/

0

# File and URL Operations

1

2

Secure file access through signed URLs and file URL management for media and document handling.

3

4

## Capabilities

5

6

### Get Signed File URLs

7

8

Generates signed URLs for secure access to files hosted on Notion's servers. Required for accessing files in private workspaces or when authentication is needed.

9

10

```typescript { .api }

11

/**

12

* Generates signed URLs for secure file access

13

* @param urls - Array of URL requests with permission records

14

* @param kyOptions - HTTP client options

15

* @returns Promise resolving to signed URLs response

16

*/

17

async getSignedFileUrls(

18

urls: SignedUrlRequest[],

19

kyOptions?: KyOptions

20

): Promise<SignedUrlResponse>;

21

22

interface SignedUrlRequest {

23

/** Permission record for the file */

24

permissionRecord: PermissionRecord;

25

/** Original file URL to sign */

26

url: string;

27

}

28

29

interface PermissionRecord {

30

/** Table type - typically 'block' for file blocks */

31

table: string;

32

/** Block ID containing the file */

33

id: string;

34

}

35

36

interface SignedUrlResponse {

37

/** Array of signed URLs corresponding to input requests */

38

signedUrls: string[];

39

}

40

```

41

42

**Usage Examples:**

43

44

```typescript

45

import { NotionAPI } from "notion-client";

46

47

const api = new NotionAPI({ authToken: "your-token" });

48

49

// Get a page with file blocks

50

const page = await api.getPage("page-with-files");

51

52

// Find file blocks and prepare URL requests

53

const urlRequests: SignedUrlRequest[] = [];

54

55

Object.entries(page.block).forEach(([blockId, record]) => {

56

const block = record.value;

57

58

if (block.type === "image" || block.type === "file" || block.type === "pdf") {

59

const fileUrl = block.properties?.source?.[0]?.[0];

60

61

if (fileUrl && fileUrl.includes("secure.notion-static.com")) {

62

urlRequests.push({

63

permissionRecord: {

64

table: "block",

65

id: blockId

66

},

67

url: fileUrl

68

});

69

}

70

}

71

});

72

73

// Get signed URLs

74

const signedResponse = await api.getSignedFileUrls(urlRequests);

75

76

// Map signed URLs back to blocks

77

signedResponse.signedUrls.forEach((signedUrl, index) => {

78

const originalRequest = urlRequests[index];

79

console.log(`Block ${originalRequest.permissionRecord.id}: ${signedUrl}`);

80

});

81

```

82

83

### Add Signed URLs

84

85

Automatically adds signed URLs to a record map for all file blocks that require them. This is typically called internally by `getPage()` when `signFileUrls` is true.

86

87

```typescript { .api }

88

/**

89

* Adds signed URLs to record map for file blocks

90

* @param options - Configuration for URL signing

91

* @returns Promise that resolves when URLs are added

92

*/

93

async addSignedUrls(options: AddSignedUrlsOptions): Promise<void>;

94

95

interface AddSignedUrlsOptions {

96

/** The record map to modify */

97

recordMap: ExtendedRecordMap;

98

/** Optional specific block IDs to process */

99

contentBlockIds?: string[];

100

/** HTTP client options */

101

kyOptions?: KyOptions;

102

}

103

104

interface ExtendedRecordMap {

105

block: Record<string, BlockRecord>;

106

collection: Record<string, CollectionRecord>;

107

collection_view: Record<string, CollectionViewRecord>;

108

notion_user: Record<string, UserRecord>;

109

collection_query: Record<string, any>;

110

/** Signed URLs mapped by block ID */

111

signed_urls: Record<string, string>;

112

}

113

```

114

115

**Usage Examples:**

116

117

```typescript

118

import { NotionAPI } from "notion-client";

119

120

const api = new NotionAPI({ authToken: "your-token" });

121

122

// Get page without automatic URL signing

123

const page = await api.getPage("page-id", { signFileUrls: false });

124

125

// Manually add signed URLs

126

await api.addSignedUrls({ recordMap: page });

127

128

// Now access signed URLs

129

Object.entries(page.signed_urls).forEach(([blockId, signedUrl]) => {

130

console.log(`Signed URL for block ${blockId}: ${signedUrl}`);

131

});

132

133

// Add signed URLs for specific blocks only

134

const specificBlockIds = ["block-1", "block-2"];

135

await api.addSignedUrls({

136

recordMap: page,

137

contentBlockIds: specificBlockIds

138

});

139

```

140

141

## File Type Support

142

143

Notion Client supports signing URLs for various file types:

144

145

**Supported File Block Types:**

146

147

```typescript

148

// File types that support signed URLs

149

const supportedFileTypes = [

150

"image", // Image files (JPG, PNG, GIF, etc.)

151

"file", // Generic file attachments

152

"pdf", // PDF documents

153

"video", // Video files

154

"audio", // Audio files

155

"page" // Pages with cover images

156

];

157

```

158

159

**Usage Examples:**

160

161

```typescript

162

const page = await api.getPage("page-id");

163

164

// Process different file types

165

Object.entries(page.block).forEach(([blockId, record]) => {

166

const block = record.value;

167

const signedUrl = page.signed_urls[blockId];

168

169

switch (block.type) {

170

case "image":

171

console.log("Image file:", signedUrl);

172

console.log("Alt text:", block.properties?.caption);

173

break;

174

175

case "file":

176

console.log("File attachment:", signedUrl);

177

console.log("Filename:", block.properties?.title);

178

break;

179

180

case "pdf":

181

console.log("PDF document:", signedUrl);

182

break;

183

184

case "video":

185

console.log("Video file:", signedUrl);

186

break;

187

188

case "audio":

189

console.log("Audio file:", signedUrl);

190

break;

191

192

case "page":

193

if (block.format?.page_cover) {

194

console.log("Page cover:", signedUrl);

195

}

196

break;

197

}

198

});

199

```

200

201

## URL Security and Expiration

202

203

Signed URLs have security considerations and expiration times:

204

205

**Usage Examples:**

206

207

```typescript

208

// Signed URLs are temporary - cache appropriately

209

const urlCache = new Map<string, { url: string; timestamp: number }>();

210

211

async function getCachedSignedUrl(blockId: string, originalUrl: string): Promise<string> {

212

const cached = urlCache.get(blockId);

213

const now = Date.now();

214

215

// URLs typically expire after several hours

216

if (cached && (now - cached.timestamp) < 6 * 60 * 60 * 1000) {

217

return cached.url;

218

}

219

220

// Generate new signed URL

221

const response = await api.getSignedFileUrls([{

222

permissionRecord: { table: "block", id: blockId },

223

url: originalUrl

224

}]);

225

226

const signedUrl = response.signedUrls[0];

227

urlCache.set(blockId, { url: signedUrl, timestamp: now });

228

229

return signedUrl;

230

}

231

```

232

233

## File URL Patterns

234

235

Different URL patterns indicate different file storage systems:

236

237

**Usage Examples:**

238

239

```typescript

240

function analyzeFileUrl(url: string): string {

241

if (url.includes("secure.notion-static.com")) {

242

return "secure-static"; // Requires signing

243

} else if (url.includes("prod-files-secure")) {

244

return "prod-secure"; // Requires signing

245

} else if (url.includes("attachment:")) {

246

return "attachment"; // Requires signing

247

} else if (url.startsWith("http")) {

248

return "external"; // External URL, no signing needed

249

} else {

250

return "unknown";

251

}

252

}

253

254

// Process files based on URL type

255

Object.entries(page.block).forEach(([blockId, record]) => {

256

const block = record.value;

257

const fileUrl = block.properties?.source?.[0]?.[0];

258

259

if (fileUrl) {

260

const urlType = analyzeFileUrl(fileUrl);

261

console.log(`Block ${blockId} (${block.type}): ${urlType}`);

262

263

if (urlType === "external") {

264

console.log("External file, no signing needed:", fileUrl);

265

} else if (page.signed_urls[blockId]) {

266

console.log("Signed URL available:", page.signed_urls[blockId]);

267

} else {

268

console.log("Signing required but not available");

269

}

270

}

271

});

272

```

273

274

## Error Handling

275

276

Handle various errors that can occur with file operations:

277

278

**Usage Examples:**

279

280

```typescript

281

try {

282

const signedResponse = await api.getSignedFileUrls(urlRequests);

283

284

// Check if all URLs were signed successfully

285

if (signedResponse.signedUrls.length !== urlRequests.length) {

286

console.warn("Some URLs could not be signed");

287

}

288

289

// Check for null/empty signed URLs

290

signedResponse.signedUrls.forEach((url, index) => {

291

if (!url) {

292

const originalRequest = urlRequests[index];

293

console.warn(`Failed to sign URL for block ${originalRequest.permissionRecord.id}`);

294

}

295

});

296

297

} catch (error) {

298

if (error.message.includes("permission")) {

299

console.error("Insufficient permissions to access files");

300

} else if (error.message.includes("not found")) {

301

console.error("File or block not found");

302

} else {

303

console.error("File signing failed:", error.message);

304

}

305

}

306

307

// Handle addSignedUrls errors

308

try {

309

await api.addSignedUrls({ recordMap: page });

310

} catch (error) {

311

console.warn("Some files could not be signed:", error.message);

312

// Page is still usable, just some files may not be accessible

313

}

314

```

315

316

## Types

317

318

```typescript { .api }

319

interface BlockRecord {

320

role: string;

321

value: Block;

322

}

323

324

interface Block {

325

id: string;

326

type: string;

327

properties?: Record<string, any>;

328

format?: Record<string, any>;

329

content?: string[];

330

created_time: number;

331

last_edited_time: number;

332

parent_id: string;

333

parent_table: string;

334

}

335

```