read_result
Lea un resultado que era demasiado grande para devolverse en línea. Cuando el JSON de una herramienta supera max_inline_chars, su respuesta incluye un preview, un result_handle y truncated: true; pase ese handle aquí para recortar el texto, buscar en él, recorrerlo por líneas o leer una sola ruta JSON, sin volver a descargar la página ni pagar de nuevo el precio de la herramienta.
Casos de uso
Encontrar la sección que necesita
Ejecute un search con un encabezado o una frase y después un slice desde el offset de la coincidencia: dos llamadas que leen una sección de una página larga en lugar del resultado entero.
Recorrer textos largos
lines devuelve una ventana de líneas con has_more, de modo que un documento markdown grande se lee en ventanas que su contexto puede contener.
Extraer un solo campo de un JSON grande
json_path lee una única ruta de un resultado almacenado de crawl_deep, batch_scrape o deep_research, o del cuerpo analizado de un fetch_url en JSON.
Nunca volver a descargar
El resultado almacenado es el que ya pagó. Leerlo cuesta 1 credit por llamada; volver a ejecutar la herramienta cuesta su precio completo y vuelve a visitar el sitio.
Endpoint
/api/v1/tools/read_resultParameters
handle procede de una respuesta con truncated: true: esta herramienta nunca descarga una página, solo lee un resultado que usted ya tiene. Un handle caduca 1 hora después de almacenarse el resultado.| Name | Type | Required | Default | Description |
|---|---|---|---|---|
handle | string | Required | - | El `result_handle` de una respuesta truncada (`res_` seguido de un UUID). Solo puede leerlo la cuenta que lo creó, durante 1 hora. Example: res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c |
operation | string | Required | - | `slice` devuelve un rango de caracteres de la vista de texto; `search` busca una subcadena literal sin distinguir mayúsculas (nunca una expresión regular) y devuelve cada coincidencia con contexto; `lines` devuelve una ventana de líneas; `json_path` lee una ruta del JSON almacenado. Example: search |
offset | number | Optional | 0 | `slice`: primer carácter que se devuelve. `lines`: índice de la primera línea que se devuelve. Example: 18240 |
length | number | Optional | 10000 | `slice`: número de caracteres que se devuelven (10.000 por defecto). `lines`: número de líneas que se devuelven (200 por defecto, 5.000 como máximo). Example: 4000 |
query | string | Optional | - | Solo para `search`, y obligatorio ahí: el texto literal que se busca, sin distinguir mayúsculas. Example: rate limits |
max_matches | number | Optional | 20 | `search`: número máximo de coincidencias devueltas, 1-100. `truncated: true` en la respuesta indica que hubo más coincidencias de las devueltas. Example: 5 |
path | string | Optional | - | Solo para `json_path`, y obligatorio ahí: claves con puntos e índices de array, en forma de puntos o de corchetes (`pages.0.url` o `pages[0].url`); sin comodines, filtros ni rangos. Se lee del objeto de resultado almacenado, o del cuerpo analizado cuando el texto almacenado es JSON, como el cuerpo de un `fetch_url`. Example: pages.0.url |
max_inline_chars | number | Optional | 40000 | Cantidad máxima de texto que se devuelve en línea, de 1.000 a 10.000.000 caracteres; cada operación limita ahí el texto que devuelve. En `json_path`, un `value` mayor llega como `value: null` con un `preview`, `truncated: true` y un aviso para acotar la ruta. Example: 40000 |
Operaciones
Cada respuesta incluye handle, tool, operation, view, view_path, total_chars y expires_at, seguidos de los campos de la operación solicitada.
offset, length, text y has_more. Por defecto, los primeros 10.000 caracteres.query, matches (cada una con offset, length, context_offset y context, 200 caracteres a cada lado), total_matches y truncated.first_line, line_count, total_lines, char_offset, lines y has_more. offset es el índice de la primera línea y length el número de líneas (200 por defecto, 5.000 como máximo).path, value y value_chars. Por encima de max_inline_chars, el valor se sustituye por value: null, un preview y truncated: true, con un aviso para acotar la ruta.Dónde vive un resultado almacenado
~/.crawlforge/results/ (TTL de 1 hora, LRU de 200 MB), y no se sube nada. Las respuestas de error nunca se almacenan, y los trabajos de batch_scrape comparten el mismo almacén.Ejemplos de solicitud
cURL
# 1. A scrape whose JSON exceeded max_inline_chars (default 40,000) came back
# with a preview instead of the markdown:
# "truncated": true,
# "result_handle": "res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c",
# "total_chars": 182406
# 2. Search the stored result for the section you need (1 credit)
curl -X POST https://crawlforge.dev/api/v1/tools/read_result \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"handle": "res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c",
"operation": "search",
"query": "rate limits",
"max_matches": 5
}'
# 3. Read the section at the first match offset (1 credit) — no second fetch
curl -X POST https://crawlforge.dev/api/v1/tools/read_result \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"handle": "res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c",
"operation": "slice",
"offset": 18240,
"length": 4000
}'TypeScript
// npm install crawlforge-sdk
import { CrawlForge } from 'crawlforge-sdk';
const client = new CrawlForge({ apiKey: process.env.CRAWLFORGE_API_KEY });
// 1. Scrape a long page. Over max_inline_chars (default 40,000 characters of
// JSON) the markdown is replaced by a preview and a result_handle.
const page = await client.scrape({
url: 'https://example.com/docs/api',
formats: ['markdown'],
});
// page.data is untyped in crawlforge-sdk 0.1 — a truncated result carries the
// handle fields shown in the cURL tab instead of the markdown.
const scraped = page.data as
| { truncated: true; result_handle: string; total_chars: number }
| { truncated?: false; formats: { markdown: string } };
if (scraped.truncated) {
const { result_handle, total_chars } = scraped;
console.log('Stored ' + total_chars + ' characters as ' + result_handle);
// 2. Search the stored markdown for the section you need (1 credit).
const found = await client.readResult({
handle: result_handle,
operation: 'search',
query: 'rate limits',
max_matches: 5,
});
// result.data is untyped in crawlforge-sdk 0.1 — its shape is the Response Example below.
const { matches } = found.data as { matches: { offset: number }[] };
// 3. Slice from the first match offset (1 credit) — no second fetch.
const [first] = matches;
if (first) {
const section = await client.readResult({
handle: result_handle,
operation: 'slice',
offset: first.offset,
length: 4000,
});
const { text, has_more } = section.data as { text: string; has_more: boolean };
console.log(text);
console.log('More after this slice:', has_more);
}
} else {
console.log(scraped.formats.markdown); // small enough to arrive inline
}Python
# pip install crawlforge
from crawlforge import CrawlForge
client = CrawlForge() # reads CRAWLFORGE_API_KEY
# 1. Scrape a long page. Over max_inline_chars (default 40,000 characters of
# JSON) the markdown is replaced by a preview and a result_handle.
page = client.scrape(url='https://example.com/docs/api', formats=['markdown'])
# result.data is a plain dict — its shape is the Response Example below.
if page.data.get('truncated'):
handle = page.data['result_handle']
print(f"Stored {page.data['total_chars']} characters as {handle}")
# 2. Search the stored markdown for the section you need (1 credit).
found = client.read_result(
handle=handle,
operation='search',
query='rate limits',
max_matches=5,
)
# 3. Slice from the first match offset (1 credit) - no second fetch.
if found.data['matches']:
first = found.data['matches'][0]
section = client.read_result(
handle=handle,
operation='slice',
offset=first['offset'],
length=4000,
)
print(section.data['text'])
print('More after this slice:', section.data['has_more'])
else:
print(page.data['formats']['markdown']) # small enough to arrive inlineEjemplo de respuesta
{ "success": true, "data": { "handle": "res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c", "tool": "scrape", "operation": "search", "view": "text", "view_path": "markdown", "total_chars": 182406, "expires_at": "2026-09-05T15:42:10.000Z", "query": "rate limits", "matches": [ { "offset": 18240, "length": 11, "context_offset": 18040, "context": "…request. Every plan is metered per API key.\n\n## Rate limits\n\nEach key may make one request per second on the Free plan…" }, { "offset": 61377, "length": 11, "context_offset": 61177, "context": "…returns 429 with a Retry-After header; see the rate limits table above for the per-plan ceilings…" } ], "total_matches": 2, "truncated": false }, "credits_used": 1, "credits_remaining": 998, "processing_time": 42}data.handleEl handle que envió, devuelto tal cualdata.toolLa herramienta que produjo el resultado almacenadodata.view`text` cuando la operación leyó la vista de texto (el markdown en scrape, el cuerpo en fetch_url); `json` en `json_path`data.view_pathQué campo del resultado original es la vista de textodata.total_charsTamaño de la vista de texto almacenada, en caracteresdata.expires_atCuándo se elimina el resultado almacenado: 1 hora después de almacenarsedata.matchesUna entrada por coincidencia: `offset` y `length` indexan la vista de texto; `context` lleva hasta 200 caracteres a cada lado, empezando en `context_offset`data.total_matchesCuántas coincidencias existen en total; `truncated` es true cuando hubo más de las que `max_matches` permitíacredits_usedCredits descontados por esta solicitud (1 por lectura)credits_remainingSu saldo de credits restanteManejo de errores
Entrada no válida (400 Bad Request)
Falta handle, operation no es slice, search, lines ni json_path, se envió search sin query o json_path sin path, o max_matches queda fuera del rango 1-100 (VALIDATION_ERROR).
Ruta no encontrada (400 Bad Request)
json_path no pudo resolver path (PATH_NOT_FOUND); el error nombra las claves disponibles en el punto donde se detuvo. No se descuentan credits.
Resultado no encontrado (404 Not Found)
Handle de resultado desconocido o caducado (RESULT_NOT_FOUND): los resultados se conservan 1 hora y solo puede leerlos la cuenta que los creó. No se descuentan credits.
Almacenamiento no disponible (503 Service Unavailable)
No se pudo acceder al almacén de resultados (STORAGE_UNAVAILABLE). Reintente en breve; no se cobra nada por una lectura que no se completó.
Credits insuficientes (402 Payment Required)
Su cuenta no tiene suficientes credits. Compre más credits o mejore su plan.
Límite de velocidad superado (429 Too Many Requests)
Ha superado el límite de velocidad de su plan. Espere un momento o mejore su plan para obtener límites más altos.
search y después slice en el offset de la coincidencia leen una sección por 2 credits. Recorrer el resultado entero en trozos de 10.000 caracteres cuesta 1 credit por trozo, y volver a ejecutar la herramienta cuesta su precio completo más otra visita al sitio.Coste en credits
Plan Free: 1,000 credits por única vez = 1.000 solicitudes
Plan Hobby: 5.000 credits/mes = 5.000 solicitudes (19 USD/mes)
Plan Professional: 50.000 credits/mes = 50.000 solicitudes (99 USD/mes)
Plan Business: 250.000 credits/mes = 250.000 solicitudes (399 USD/mes)
Herramientas relacionadas
result_handle: max_inline_chars fija cuánto llega en línea (2 credits)json_path lee una página del rastreo almacenado (4 credits)json_path lee su cuerpo analizado (1 credit)batch_scrape; los resultados almacenados del lote comparten el mismo almacén de 1 hora (1 credit)