YouTube Transcripts MCP
Transcripciones de YouTube, búsqueda de videos, capítulos y resúmenes extractivos para agentes de IA. Sin clave de API de YouTube, sin LLM. Alojado en Apify (token Bearer de Apify).
Servidor MCP alojado
npx add-mcp 'https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcp'Se instala en Claude Code, Codex, Cursor y más
Documentación

Servidor MCP de transcripciones de YouTube
Versión alojada (sin configuración): apify.com/parallelstudio/youtube-transcripts-mcp. Conecta cualquier cliente MCP a
https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcpcon tu token de Apify y paga por llamada. Este repositorio es el código de código abierto; también puedes ejecutarlo localmente connpm install && npm run build && node dist/index.js(stdio).
Dale a tu agente de IA transcripciones, búsqueda, capítulos y resúmenes de YouTube. Este servidor MCP obtiene los subtítulos de cualquier vídeo público de YouTube (como texto plano, segmentos con marca de tiempo o SRT) junto con sus metadatos. También busca en YouTube, devuelve capítulos y crea resúmenes extractivos estructurados en los que cada punto enlaza al momento del que procede.
Funciona con Claude Desktop, Claude Code, Cursor, VS Code, Windsurf y cualquier otro cliente que admita MCP sobre Streamable HTTP. No necesitas una clave de API de YouTube ni una cuenta de Google. En el servidor no se ejecuta ningún LLM, así que no pagas los tokens de nadie más.
Qué puedes hacer con él
- Investigar más rápido. "Resume este pódcast de 2 horas y dame los 10 puntos clave con marcas de tiempo."
- Responder preguntas sobre un vídeo. Carga la transcripción y deja que tu agente cite y haga referencia a momentos exactos.
- Navegar por vídeos largos. Obtén los capítulos del creador. Si el vídeo no tiene ninguno, obtén segmentos temáticos derivados de la transcripción.
- Encontrar fuentes. Busca en YouTube con filtros por fecha de subida, duración, orden y "tiene subtítulos", y luego obtén las transcripciones de los mejores resultados.
- Reutilizar contenido. Exporta subtítulos SRT, crea notas del programa o redacta una entrada de blog a partir de una charla, usando cualquier idioma de subtítulos o la traducción automática de YouTube.
Herramientas
| Herramienta | Qué hace | Parámetros clave |
|---|---|---|
get_transcript | Devuelve la transcripción más los metadatos: título, canal, duración, visualizaciones, fecha de publicación, categoría, los idiomas de subtítulos disponibles y si la pista devuelta es generada automáticamente. La salida puede ser texto plano, párrafos con marca de tiempo [m:ss], segmentos {start, duration, text} o SRT. YouTube también puede traducirla automáticamente. | video, languages (lista de prioridad, por defecto ["en"]), format (text | segments | srt), include_timestamps, translate_to, allow_auto_generated, fallback_to_any_language, max_chars |
search_videos | Busca en YouTube mediante la misma API de búsqueda InnerTube que usa el sitio web. Devuelve id, título, canal, duración, visualizaciones, tiempo de publicación relativo, fragmento de descripción, miniatura, indicador de directo e insignias. Obtiene más páginas automáticamente hasta limit. | query, limit (1–50), upload_date (hour…year), duration (short <4 min, medium 4–20 min, long >20 min), sort_by, features (p. ej. subtitles, live, hd, creative_commons), hl, gl |
get_video_chapters | Devuelve capítulos con horas de inicio y fin y enlaces directos. Usa los capítulos del creador de la descripción o los marcadores de capítulo de YouTube. Si no hay ninguno, deriva segmentos temáticos de la transcripción y titula cada uno con sus palabras clave distintivas y una frase representativa. | video, source (auto | description | transcript), languages, target_chapters |
summarize_transcript | Crea un resumen extractivo estructurado sin un LLM: un TL;DR, puntos clave sin duplicados, palabras clave y frases clave, viñetas por capítulo y citas destacadas. Cada elemento tiene una marca de tiempo y un enlace …&t=123s. | video, max_points (1–30, por defecto 8), languages, bullets_per_chapter, max_quotes, keyword_limit |
video acepta un id de 11 caracteres (dQw4w9WgXcQ) o cualquier URL de YouTube: watch?v=, youtu.be/, /shorts/, /embed/, /live/, m. y hosts music., y youtube-nocookie.com.
Todas las herramientas son de solo lectura.
Códigos de error
Los fallos se devuelven como errores de herramienta MCP (isError: true). El texto empieza por Error (<CODE>): … y structuredContent.error contiene { code, message, retryable, details }, de modo que tu agente puede reaccionar al código en lugar de analizar el mensaje:
| Código | Significado |
|---|---|
INVALID_VIDEO_ID / INVALID_INPUT | El id/URL del vídeo u otro argumento no es válido |
VIDEO_UNAVAILABLE | Eliminado, nunca existió o el id es incorrecto |
VIDEO_PRIVATE | El vídeo es privado |
AGE_RESTRICTED | YouTube requiere una cuenta con sesión iniciada (restricción de edad) |
VIDEO_UNPLAYABLE | Solo para miembros, bloqueado por región, directo programado, etc. (details.reason contiene el texto de YouTube) |
TRANSCRIPTS_DISABLED | El vídeo no tiene ningún subtítulo |
NO_TRANSCRIPT_FOUND | Ninguno de los idiomas solicitados existe y fallback_to_any_language es false. details.available enumera las pistas que sí existen |
TRANSLATION_UNAVAILABLE | YouTube no puede traducir esta pista a translate_to |
NO_CHAPTERS | Se solicitó source: "description", pero la descripción no tiene lista de capítulos |
REQUEST_BLOCKED | YouTube bloqueó la solicitud (HTTP 429, reCAPTCHA o "confirma que no eres un bot") incluso después de que el servidor rotara las sesiones de proxy. retryable: true |
PO_TOKEN_REQUIRED / EMPTY_TRANSCRIPT | YouTube sirvió una URL de subtítulos que requiere un token PO, o un archivo de subtítulos vacío |
UPSTREAM_ERROR / INTERNAL_ERROR | Tiempos de espera agotados, respuestas 5xx o formatos de respuesta inesperados |
Precios
Este Actor usa precios de pago por evento. Los eventos de herramienta solo se cobran por llamadas exitosas. Las llamadas fallidas no se cobran como eventos de herramienta, ya sea porque fallan en la validación, porque el vídeo es privado o no tiene subtítulos, o porque YouTube bloquea la solicitud. Apify también cobra su evento estándar de inicio de Actor ($0.00005 por GB de memoria, mínimo un evento) cada vez que se inicia una instancia del servidor. Con la memoria en espera predeterminada de 512 MB, eso es $0.00005 por inicio, y una instancia en ejecución atiende muchas llamadas antes de quedar inactiva. La pestaña Pricing de este Actor es la lista autoritativa de los precios actuales.
| Evento | Precio | Cuándo se cobra | Recuento |
|---|---|---|---|
transcript | $0.005 | Cada llamada exitosa a get_transcript | 1 por llamada, independientemente de la duración o el formato del vídeo |
search | $0.003 | Cada llamada exitosa a search_videos | 1 por llamada (hasta 50 resultados, incluidas las páginas adicionales obtenidas automáticamente) |
chapters | $0.008 | Cada llamada exitosa a get_video_chapters | 1 por llamada, desde la descripción o derivada de la transcripción |
summary | $0.015 | Cada llamada exitosa a summarize_transcript | 1 por llamada. La transcripción obtenida internamente no se cobra por separado como transcript |
Una búsqueda que devuelve cero resultados sigue siendo una llamada exitosa y se cobra como un evento search.
Conecta tu cliente MCP
Endpoint (Streamable HTTP):
https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcp
Autentícate con tu token de API de Apify enviándolo como Authorization: Bearer <APIFY_TOKEN>.
Cursor (~/.cursor/mcp.json o .cursor/mcp.json)
{
"mcpServers": {
"youtube-transcripts": {
"url": "https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcp",
"headers": {
"Authorization": "Bearer <APIFY_TOKEN>"
}
}
}
}
Windsurf (~/.codeium/windsurf/mcp_config.json)
{
"mcpServers": {
"youtube-transcripts": {
"serverUrl": "https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcp",
"headers": {
"Authorization": "Bearer <APIFY_TOKEN>"
}
}
}
}
Claude Desktop (claude_desktop_config.json)
El archivo de configuración de Claude Desktop solo lanza servidores locales (stdio), así que conecta con el endpoint alojado mediante mcp-remote (requiere Node.js). El mismo bloque funciona en cualquier otro cliente que solo admita stdio:
{
"mcpServers": {
"youtube-transcripts": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcp",
"--header", "Authorization: Bearer ${APIFY_TOKEN}"
],
"env": { "APIFY_TOKEN": "<APIFY_TOKEN>" }
}
}
}
Clientes que no pueden enviar cabeceras
Apify también acepta el token como parámetro de consulta token, de modo que un cliente que solo acepta una URL puede usar:
https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcp?token=<APIFY_TOKEN>
Prefiere la cabecera Authorization cuando tu cliente la admita: un token en una URL puede acabar en los registros. Un token de Apify con alcance limitado limita el daño si se filtra.
Claude Code
claude mcp add --transport http youtube-transcripts \
https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcp \
--header "Authorization: Bearer $APIFY_TOKEN"
VS Code (.vscode/mcp.json)
{
"servers": {
"youtube-transcripts": {
"type": "http",
"url": "https://parallelstudio--youtube-transcripts-mcp.apify.actor/mcp",
"headers": { "Authorization": "Bearer ${input:apifyToken}" }
}
},
"inputs": [{ "id": "apifyToken", "type": "promptString", "description": "Apify API token", "password": true }]
}
Prompts de ejemplo
- "Obtén la transcripción de https://youtu.be/aircAruvnKk y explica la parte sobre el descenso de gradiente, citando marcas de tiempo."
- "Busca charlas sobre Rust async del último año, de más de 20 minutos y con subtítulos, y luego resume las 3 mejores."
- "Dame los capítulos de este directo de 3 horas. Si no tiene ninguno, divídelo por tema."
- "Exporta los subtítulos en alemán de este vídeo como SRT. Tradúcelos si no hay pista en alemán."
Ejemplos de salida
Los extractos siguientes provienen de los vídeos de prueba incluidos que se usan en la suite de tests (consulta demo/transcript.md para ver cada llamada y respuesta).
get_transcript { "video": "kM3pLz8QwT0", "format": "segments", "include_timestamps": true } (recortado):
{
"video": {
"id": "kM3pLz8QwT0",
"url": "https://www.youtube.com/watch?v=kM3pLz8QwT0",
"title": "How Solar Panels Actually Work (Explained Simply)",
"channel": "Bright Circuits",
"durationSeconds": 552,
"duration": "9:12",
"viewCount": 1843291,
"publishDate": "2025-03-14T07:00:12-07:00",
"category": "Education",
"isLive": false
},
"transcript": {
"language": "en",
"languageName": "English",
"autoGenerated": false,
"translatedFrom": null,
"matchedPreference": true,
"format": "segments",
"segmentCount": 128,
"wordCount": 870,
"truncated": false,
"segments": [
{ "start": 0, "duration": 4.116, "time": "0:00", "text": "Hey everyone, welcome back to Bright Circuits." },
{ "start": 4.2, "duration": 4.116, "time": "0:04", "text": "Today we're answering a question I get" }
]
},
"availableLanguages": [
{ "languageCode": "en", "languageName": "English", "autoGenerated": false, "translatable": true },
{ "languageCode": "es", "languageName": "Spanish", "autoGenerated": false, "translatable": true },
{ "languageCode": "en", "languageName": "English", "autoGenerated": true, "translatable": true }
],
"translationLanguagesCount": 18
}
get_video_chapters en un vídeo sin capítulos del creador (solo subtítulos automáticos). Los capítulos se derivan de la transcripción:
| # | inicio | título (palabras clave distintivas) |
|---|---|---|
| 1 | 0:00 | Masa madre, agua, gramos |
| 2 | 2:00 | Autólisis, gramos, templado |
| 3 | 4:11 | Estirar, pliegues, fermentación en bloque |
| 4 | 5:52 | Frío, lados, superficie |
| 5 | 9:34 | Tapa |
| 6 | 10:30 | Enfriar, corte, miga |
summarize_transcript { "video": "kM3pLz8QwT0", "max_points": 6 } (extracto):
- Puntos clave: [2:32] Cuando un fotón con suficiente energía golpea el silicio, libera un electrón y deja un hueco detrás. · [5:09] Los paneles solares producen corriente continua, pero tu hogar y la red eléctrica funcionan con corriente alterna. · [6:52] Una batería doméstica almacena el excedente de energía solar durante el día para que puedas usarlo por la noche…
- Palabras clave: paneles, celdas, inversor, fotones, solar, energía, batería, corriente, panel solar, electrones
- Las viñetas por capítulo usan los 7 capítulos del creador ("Dentro de una celda solar de silicio", "Inversores y la red"…), con 2 viñetas cada uno.
- Citas: "El inversor convierte la corriente continua en corriente alterna que coincide exactamente con el voltaje y la frecuencia de la red." [5:18]
Cómo funciona
Transcripciones
El servidor obtiene las transcripciones de la misma forma que la popular biblioteca de Python youtube-transcript-api:
- Carga la página de visualización. Si YouTube muestra la página de consentimiento de cookies de la UE, la acepta. Una página de reCAPTCHA se notifica como
REQUEST_BLOCKED. La página también proporciona la clave de API de InnerTube, la fecha de publicación, la categoría y los marcadores de capítulo. - Llama a la API player de InnerTube con el contexto de cliente ANDROID. Esto devuelve los detalles del vídeo y el
captionTracks. El estado de reproducibilidad se asigna a los códigos de error anteriores: privado, con restricción de edad, no disponible, no reproducible o "confirma que no eres un bot". - Elige una pista. Para cada idioma preferido en orden, los subtítulos manuales tienen prioridad sobre los generados automáticamente, y una coincidencia exacta de código tiene prioridad sobre una coincidencia de idioma base (
en-GB→en). Luego descarga el archivo timedtext. El servidor analiza XML srv1, XML srv3 y json3.
Las tres solicitudes de un vídeo pasan por una sola sesión de proxy, porque YouTube firma las URL de subtítulos para la IP que las solicitó.
Superar los bloqueos de IP de YouTube
YouTube bloquea la mayoría de las IP de la nube. En una prueba de viabilidad desde Apify, las solicitudes directas obtuvieron 1 de 5 vídeos de prueba. Con el proxy de centro de datos de Apify, 4 de 5 funcionaron, y el quinto vídeo simplemente no tiene transcripción. Por eso, en la plataforma de Apify este servidor enruta cada solicitud a YouTube a través de Apify Proxy (centro de datos por defecto), usando sesiones fijas y un undici ProxyAgent. Cuando YouTube bloquea una sesión (429, captcha, comprobación de bot, archivo de subtítulos vacío, error de red), el servidor descarta esa sesión y reintenta con una nueva IP, hasta 5 sesiones por llamada. Una sesión saludable se reutiliza para llamadas posteriores.
Capítulos derivados de la transcripción
Cuando un vídeo no tiene capítulos del creador, el servidor segmenta la transcripción al estilo TextTiling:
- Divide la transcripción en bloques de tiempo de 20 a 60 s y construye un vector TF-IDF para cada bloque.
- En cada hueco entre bloques, compara el vocabulario de los dos bloques anteriores con el de los dos bloques posteriores (similitud del coseno). Una caída pronunciada significa que cambió el tema.
- Conserva las caídas más profundas, sujeto a una longitud mínima de capítulo. Luego divide cualquier capítulo que siga siendo mucho más largo que la media (respaldo por ventana temporal).
- Ajusta cada límite al inicio de la oración más cercano. Titula cada capítulo con las palabras clave que lo distinguen de los demás capítulos y adjunta su oración más central.
Resúmenes sin un LLM
- Construye oraciones. Los subtítulos con puntuación se dividen según la puntuación. Los subtítulos automáticos no tienen puntuación, así que se reajustan palabra por palabra y se cortan en fragmentos cortos en los marcadores del discurso ("so", "now", "next"…) y en las pausas. Se eliminan las etiquetas al estilo
[Music]. - Clasifica las oraciones con TextRank: PageRank sobre un grafo ponderado por la similitud del coseno de TF-IDF. Los fragmentos muy cortos se ponderan a la baja.
- Elige los puntos clave, las viñetas de los capítulos y las citas con MMR (Maximal Marginal Relevance), para que los casi duplicados no se repitan. Luego vuelve a ponerlos en orden cronológico.
- Puntúa las palabras clave por frecuencia, ponderadas por un IDF sobre ventanas de 1 minuto. Los unigramas ya cubiertos por un bigrama seleccionado se descartan.
El método es determinista y transparente, y es rápido: milisegundos después de obtener la transcripción. Como cita las propias palabras del hablante, no puede alucinar.
Limitaciones
- Solo subtítulos públicos. Los vídeos privados, solo para miembros y con restricción de edad fallan con códigos de error claros. El servidor nunca inicia sesión.
- La calidad depende de los subtítulos. Los subtítulos generados automáticamente no tienen puntuación y presentan algunos errores de reconocimiento. Los resúmenes y los capítulos derivados lo manejan, pero los subtítulos del creador dan mejores oraciones.
- Extractivo, no abstractivo. Los resúmenes citan oraciones de la transcripción; no las parafrasean. La lista de palabras vacías y la derivación están ajustadas para el inglés. Otros idiomas siguen funcionando, con palabras clave algo más ruidosas.
- La búsqueda solo devuelve vídeos. Se excluyen los canales, las listas de reproducción y los estantes de Shorts, y también las recomendaciones de "La gente también vio". Las fechas relativas ("hace 3 semanas") se pasan exactamente como las muestra YouTube.
- Endpoints no oficiales. InnerTube y timedtext son las API que usan las propias aplicaciones de YouTube, pero no están documentadas y pueden cambiar. El servidor reintenta con retroceso exponencial y rota los proxies. Las respuestas se almacenan en caché en memoria durante 10 minutos.
- Transmisiones en directo. Las transmisiones en directo en curso normalmente no tienen transcripción hasta que terminan.
Preguntas frecuentes
¿Necesito una clave de la API de datos de YouTube o una cuenta de Google? No. Solo necesitas una cuenta y un token de Apify para llamar a este servidor alojado.
¿Qué idiomas son compatibles?
Cualquier idioma para el que YouTube tenga subtítulos. Pasa una lista de prioridad como ["de", "en"]. Si no existe ninguno de ellos, se devuelve la mejor pista disponible junto con una nota, a menos que establezcas fallback_to_any_language: false. Con translate_to, YouTube traduce la pista automáticamente.
¿Cuánto pueden durar los vídeos?
No hay un límite fijo. Una transcripción cuesta un evento transcript sin importar su duración. Para vídeos muy largos, usa max_chars o summarize_transcript para mantener pequeño el contexto de tu agente.
¿Por qué recibí REQUEST_BLOCKED?
YouTube rechazó todas las IP de proxy que el servidor probó para esa llamada. Normalmente es temporal, así que reinténtalo tras una breve pausa. Las llamadas bloqueadas no se cobran.
¿Se almacena algún dato? No. El servidor no tiene estado y solo mantiene una breve caché en memoria. No escribe nada en conjuntos de datos ni en almacenes clave-valor.
¿Puedo alojarlo yo mismo?
Sí. Consulta más abajo. Las IP residenciales normalmente pueden llegar a YouTube directamente. En servidores en la nube, establece YT_PROXY_URL.
Autoalojamiento y desarrollo
npm install
npm run build
npm start # MCP over stdio
npm run start:http # Streamable HTTP at http://127.0.0.1:3000/mcp (GET / and /health = readiness)
npm test # build + unit tests + MCP smoke test, all offline against recorded fixtures
| variable de entorno | valor predeterminado | propósito |
|---|---|---|
PORT / HOST | 3000 / 127.0.0.1 | Enlace HTTP (--http); también --port, --host |
YT_PROXY_URL | (sin definir) | Tus propios proxies: URLs http://user:pass@host:port separadas por comas, una URL fija por sesión, por turnos |
YT_PROXY | (sin definir) | off desactiva el uso de proxies, incluso en Apify |
YT_PROXY_GROUPS / YT_PROXY_COUNTRY | datacenter / any | Grupos de Apify Proxy (p. ej. RESIDENTIAL) y código de país en la plataforma |
YT_PROXY_ATTEMPTS | 5 | Sesiones de proxy probadas por llamada antes de devolver REQUEST_BLOCKED |
YT_FIXTURES | (sin definir) | Sirve todas las solicitudes de YouTube desde un directorio de fixtures (modo sin conexión, usado por las pruebas) |
HTTP_TIMEOUT_MS | 8000 | Tiempo de espera por solicitud (una salida de proxy bloqueada se rota, no se espera) |
HTTP_MAX_RETRIES | 2 | Reintentos en el lugar ante 5xx/tiempos de espera solo para conexiones directas (sin proxy); las llamadas con proxy rotan de sesión en su lugar |
YT_KEEP_ALIVE_MS | 60000 | Keep-alive inactivo del túnel de proxy entre llamadas |
YT_SLOW_SESSION_MS | 6000 | Una sesión de proxy cuya operación tardó más se retira para que la siguiente llamada obtenga una IP nueva |
CACHE_TTL_MS | 600000 | TTL de la caché en memoria (0 lo desactiva) |
BILLING | (sin definir); apify en la imagen de Docker | apify habilita la facturación de pago por evento de Actor.charge(); log solo imprime los cargos en stderr |
En Apify (modo Standby). Cuando se establece APIFY_IS_AT_HOME, el servidor llama a Actor.init(), fuerza el modo HTTP y escucha en 0.0.0.0:$ACTOR_WEB_SERVER_PORT. GET / responde a la sonda de disponibilidad de Standby y MCP se sirve en /mcp. El tráfico de YouTube pasa por Apify Proxy. El servidor HTTP no tiene autenticación propia: en Apify, la plataforma autentica las solicitudes con tu token de API. Si lo autoalojas más allá de localhost, colócalo detrás de un proxy de autenticación.
Pruebas
- Pruebas unitarias (
test/unit/*.test.mjs,node --test) se ejecutan contra fixtures hechos a mano que reflejan respuestas reales de YouTube: JSON del reproductor ANDROID, timedtext srv1/json3, páginas de búsqueda WEB con continuaciones y estantes, páginas de visualización incluida una página de reCAPTCHA, y todos los estados de error. Cubren el análisis, la selección de pistas, la descripción y los capítulos derivados, TextRank y los resúmenes, la codificaciónparamsde búsqueda, la asignación de errores y la rotación de sesiones de proxy. - Prueba de humo de MCP (
test/smoke.mjs) inicia el servidor por stdio conYT_FIXTURES, lista las herramientas, llama a cada herramienta (incluidas las rutas de error), comprueba que la facturación solo cobra las llamadas exitosas, comprueba el modo HTTP y la sonda de disponibilidad, y escribedemo/transcript.{json,md}. - Prueba remota en vivo (
test/live-remote.mjs, no forma parte denpm test) se ejecuta contra una URL de Standby desplegada:MCP_URL=… APIFY_TOKEN=… node test/live-remote.mjs. npm run fixturesregeneratest/fixtures/a partir descripts/make-fixtures.mjs.
.actor/ actor.json (Standby, /mcp), input_schema.json (empty), output_schema.json (MCP endpoint)
Dockerfile apify/actor-node:20, multi-stage TypeScript build, BILLING=apify
src/
index.ts entry: stdio or stateless Streamable HTTP; Apify Standby wiring
server.ts MCP tool registrations (zod schemas, error payloads, billing)
youtube.ts watch page → InnerTube player (ANDROID) → timedtext; track selection; error mapping
transcript.ts srv1/srv3/json3 parsing; text / SRT formatting
search.ts InnerTube search, protobuf filter params, continuation paging
chapters.ts description chapter parser; TextTiling-style derived chapters
text.ts tokenizer, stemmer, sentences, TF-IDF, TextRank, keywords
summary.ts extractive summary (key points, chapter bullets, quotes, stats)
http.ts timeouts/retries, proxy sessions + rotation, fixture mode
proxy.ts Apify Proxy / YT_PROXY_URL selection
fixtures.ts offline fixture resolver
billing.ts chargeEvent() → Actor.charge() when BILLING=apify
util.ts error codes, cache, id/URL parsing, time helpers
Licencia
MIT