API de Notion: cómo leer páginas y tablas con Postman


1. El concepto

La API de Notion te permite leer y escribir el contenido de tu workspace desde código. A diferencia de otras APIs como la de GitHub, no existe acceso público — todo requiere autenticación, hasta la petición más simple.

Como analista, esto define el punto de partida: antes de hacer cualquier petición, necesitas crear una Integration en Notion y darle acceso explícito a las páginas que quieres consultar.


2. Qué tipo de API es Notion

Notion expone una API REST. El mismo estilo que GitHub: cada URL representa un recurso, el método HTTP define la operación, y las respuestas vienen en JSON.

La URL base de toda la API es https://api.notion.com/v1. A partir de ahí se construyen los endpoints:

https://api.notion.com/v1/search                      → buscar páginas y databases
https://api.notion.com/v1/blocks/{block_id}/children  → leer el contenido de una página
https://api.notion.com/v1/databases/{database_id}/query → consultar una database

Una diferencia importante con GitHub: Notion exige un header adicional en cada petición que no existe en otras APIs:

Notion-Version: 2026-03-11

Este header indica qué versión del contrato de la API quieres usar. Sin él, la petición falla. Es obligatorio siempre.


3. Endpoints disponibles

La API de Notion está organizada por tipo de objeto. Cada objeto tiene sus propios endpoints y métodos:

Objeto Endpoint Método Qué hace
Search /search POST Buscar páginas y databases accesibles
Pages /pages POST Crear una página nueva
Pages /pages/{id} GET Leer metadatos de una página
Pages /pages/{id} PATCH Actualizar propiedades de una página
Databases /databases/{id} GET Leer estructura de una database
Databases /databases/{id}/query POST Consultar entradas de una database con filtros
Blocks /blocks/{id}/children GET Leer el contenido de una página o bloque
Blocks /blocks/{id} PATCH Editar un bloque existente
Blocks /blocks/{id} DELETE Archivar un bloque
Users /users/me GET Ver el usuario autenticado
Comments /comments GET Leer comentarios

En esta sesión usamos tres: POST /search, GET /blocks/{page_id}/children y GET /blocks/{table_id}/children.

Por qué algunos endpoints de lectura usan POST

En REST la convención es usar GET para leer. Pero GET transfiere sus parámetros en la URL — lo que funciona bien para identificadores simples, pero se vuelve engorroso con estructuras complejas.

Notion usa POST en los endpoints de lectura que aceptan filtros. Por ejemplo, /databases/{id}/query permite enviar esto en el body:

{
  "filter": {
    "property": "Estado",
    "select": { "equals": "Comprado" }
  },
  "sorts": [
    { "property": "Precio", "direction": "descending" }
  ]
}

Ese mismo filtro en la URL sería ilegible e inmanejable. El body de un POST es la forma práctica de enviar parámetros de consulta complejos.

La regla en Notion:

  • Lectura simple por ID → GET
  • Lectura con filtros opcionales → POST
  • Crear → POST
  • Editar → PATCH
  • Archivar → DELETE

Headers

Los dos headers obligatorios en cada petición de Notion:

Header Valor
Authorization Bearer <tu_token>
Notion-Version 2026-03-11

En Postman: Authorization se configura en la pestaña Authorization → Bearer Token. Notion-Version va en la pestaña Headers como un par clave-valor.

Una aclaración sobre el nombre: el Integration Token de Notion es un Bearer Token estático — usa el header Authorization: Bearer y se genera manualmente desde la web, sin POST de login. Postman llama al campo "Bearer Token" porque ese es el formato del header. Lo que lo distingue de un Bearer Token dinámico es que es estático: no expira automáticamente y no hay que renovarlo con ninguna petición programática.

Body

Solo en métodos que envían datos: POST, PATCH. En el caso de /search con body vacío {}, la API devuelve todo lo accesible para la Integration.

Status code

Los más importantes para trabajar con Notion:

Código Significado
200 OK Petición exitosa
400 Bad Request Error en el body o parámetros enviados
401 Unauthorized Token inválido o ausente
403 Forbidden El token existe pero no tiene acceso al recurso
404 Not Found El recurso no existe o no fue compartido con la Integration
429 Too Many Requests Rate limit superado (3 peticiones por segundo)

4. Autenticación — Integration Token

Crear la Integration

Notion no usa Personal Access Tokens como GitHub. Usa Integrations — aplicaciones registradas en tu workspace que reciben un token propio.

  1. Ir a https://www.notion.so/my-integrations
  2. Clic en New integration
  3. Asignar un nombre descriptivo (ej: postman-prueba-api)
  4. Seleccionar el workspace donde va a operar
  5. Copiar el Internal Integration Token — solo se muestra una vez completo, aunque después es visible en la configuración

Capabilities — los scopes de Notion

Al crear o configurar la Integration, Notion pide definir qué puede hacer. Son el equivalente a los scopes de GitHub, pero presentados como checkboxes visuales:

Grupo Permiso Qué permite
Content Read content Ver páginas y entradas de databases
Content Update content Editar páginas y entradas existentes
Content Insert content Crear páginas y entradas nuevas
Comments Read comments Ver comentarios en páginas y bloques
Comments Insert comments Agregar comentarios
Users No user information Sin acceso a datos de usuario
Users Read user info without email Perfiles básicos, sin email
Users Read user info with email Perfiles completos con email

Regla práctica: pedir solo lo necesario. Para leer datos como analista: solo Read content.

Dar acceso a páginas

Aquí está la diferencia más importante con GitHub: el token existe, pero no puede leer nada hasta que explícitamente le des acceso a páginas.

Desde la configuración de la Integration, en la sección Content access, puedes seleccionar las páginas padre de tu workspace. La Integration tendrá acceso a esas páginas y todo su contenido anidado.


5. Tu workspace desde la API — /search

Con el token configurado en Postman, la primera petición útil es /search. Devuelve todas las páginas y databases que la Integration puede ver.

  • Método: POST
  • URL: https://api.notion.com/v1/search
  • Body: {}

La respuesta:

{
    "object": "list",
    "results": [ ... ],
    "next_cursor": null,
    "has_more": false,
    "type": "page_or_data_source"
}

La estructura raíz tiene tres campos importantes para un analista:

Campo Qué indica
results Array con las páginas y databases encontrados
next_cursor Si hay más resultados, este valor se pasa en la siguiente petición para continuar
has_more true si existen más resultados que no caben en esta respuesta

Esto es la paginación por cursor — distinta a la de GitHub, que usaba el header Link en la respuesta. Notion devuelve la información de paginación dentro del JSON.

Dentro de cada página devuelta:

{
    "object": "page",
    "id": "295477bc-d0eb-80c3-...",
    "created_time": "2025-10-23T01:56:00.000Z",
    "last_edited_time": "2026-05-17T02:41:00.000Z",
    "parent": { "type": "workspace", "workspace": true },
    "in_trash": false,
    "is_archived": false,
    "is_locked": false,
    "properties": {
        "title": { "title": [{ "plain_text": "Compras" }] }
    }
}

Campos útiles:

Campo Qué contiene
id El identificador único de la página — se usa en peticiones siguientes
created_time / last_edited_time Fechas en formato ISO 8601
parent.workspace: true La página está en el nivel raíz del workspace
in_trash / is_archived Estado de la página
properties.title El título de la página

6. El contenido de una página — /blocks/{id}/children

/search solo devuelve metadatos. Para leer el contenido real de una página — sus párrafos, tablas, imágenes — se usa un endpoint diferente:

  • Método: GET
  • URL: https://api.notion.com/v1/blocks/{page_id}/children

Donde {page_id} es el id obtenido en la respuesta de /search.

La respuesta es otra lista, pero ahora de bloques:

{
    "object": "list",
    "results": [
        { "object": "block", "type": "paragraph", ... },
        { "object": "block", "type": "table", ... },
        { "object": "block", "type": "divider", ... }
    ]
}

7. El modelo de bloques — la diferencia estructural de Notion

Este es el concepto más importante de la API de Notion y el que más diferencia tiene con GitHub.

En Notion, todo el contenido es un bloque. Un párrafo es un bloque. Una tabla es un bloque. Un separador, una imagen, un bloque de código — todos son bloques. Y cada bloque tiene la misma estructura base:

{
    "object": "block",
    "id": "...",
    "type": "<tipo>",
    "<tipo>": { ... }   ← el contenido real va aquí, bajo la clave del mismo nombre
}

Tipos de bloque y su contenido

type Qué es en Notion Dónde está el contenido
paragraph Párrafo de texto paragraph.rich_text[]
heading_1 / heading_2 / heading_3 Títulos heading_1.rich_text[]
table Tabla Solo metadatos — las filas son hijos
table_row Fila de una tabla table_row.cells[][]
divider Línea horizontal --- divider: {} — sin contenido
code Bloque de código code.rich_text[] + code.language
image Imagen image.external.url o image.file.url
bulleted_list_item Ítem de lista bulleted_list_item.rich_text[]

El rich_text — cómo Notion representa texto

El texto nunca es solo un string. Siempre es un array de objetos, aunque el párrafo sea texto plano sin formato:

"rich_text": [
    {
        "plain_text": "Para guardar fotos",
        "annotations": {
            "bold": false,
            "italic": false,
            "strikethrough": false,
            "color": "default"
        }
    }
]

Esto permite que un solo párrafo mezcle texto normal, negrita, itálica y color en segmentos distintos — cada segmento es un elemento del array. Para extraer el texto en Python, hay que iterar sobre el array y concatenar los plain_text.

Un párrafo vacío (línea en blanco en Notion) devuelve "rich_text": [].

Tablas y recursividad — el caso más importante

Una tabla no devuelve sus filas directamente. Solo devuelve metadatos:

{
    "type": "table",
    "has_children": true,
    "table": {
        "table_width": 4,
        "has_column_header": false,
        "has_row_header": false
    }
}

El campo has_children: true indica que hay contenido anidado. Para leerlo, se necesita otra petición usando el ID de la tabla:

GET https://api.notion.com/v1/blocks/{table_id}/children

Esa petición devuelve los table_row, donde cada fila tiene un array cells — uno por columna:

"table_row": {
    "cells": [
        [{ "plain_text": "Adaptador HDMI" }],
        [{ "plain_text": "Para conectar el Mac a una pantalla" }],
        [{ "plain_text": "1150" }],
        [{ "plain_text": "Comprado" }]
    ]
}

Las celdas vacías son [].

El árbol de peticiones

Leer una página completa con tablas requiere tres niveles de petición encadenada:

POST /search
  → GET /blocks/{page_id}/children      → obtienes los bloques de la página
    → GET /blocks/{table_id}/children   → obtienes las filas de cada tabla

Esto es muy distinto a GitHub, donde una sola petición GET devolvía todos los datos del recurso. En Notion, el contenido es un árbol — y para recorrerlo hay que seguir los has_children: true con peticiones adicionales.


8. Mi conclusión

La API de Notion tiene la misma base que cualquier API REST — métodos HTTP, headers, JSON — pero introduce dos conceptos que no existen en otras APIs:

  1. El header Notion-Version: obligatorio en cada petición. Sin él, la API rechaza la llamada.
  2. El modelo de bloques: el contenido no es plano. Es un árbol. Leer una página compleja puede requerir múltiples peticiones encadenadas siguiendo los has_children: true.

Para un analista, el flujo básico ya es útil: /search para descubrir qué páginas están disponibles, /blocks/{id}/children para leer el contenido. El siguiente paso natural es trabajar con databases — el objeto más potente de Notion para datos estructurados, con su propio endpoint de consulta con filtros y ordenamiento.


9. Recursos


10. Pendientes

(sección interna — se elimina antes de publicar) - [ ] Grabar video siguiendo los pasos documentados - [ ] Revisar que todos los endpoints siguen activos antes de publicar - [ ] Post siguiente: databases — POST /databases/{id}/query con filtros y ordenamiento - [ ] Post siguiente: páginas dentro de páginas — recursividad real y cómo manejarla en Python - [ ] Agregar sección de Python — extraer tabla de Notion a Excel con requests + pandas