> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abbyy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API REST

Ingest expone la API en el puerto 8080 del Service `fineparser-ingest`. Envíe los documentos, consulte el estado de los trabajos hasta que finalicen y, después, descargue los resultados.

La API no incluye autenticación. Antes de exponerla, colóquela detrás de un componente que autentique al código que realiza la llamada. Consulte [Exponer la API](https://docs.abbyy.com/es/fine-parser/enterprise/best-practices/production-deployment.md#exposing-the-api).

| Endpoint | Finalidad |
| - | - |
| `POST /parse` | Envía uno o varios documentos. |
| `GET /jobs/{jobId}` | Devuelve el estado de un trabajo. |
| `GET /batch/{batchId}` | Devuelve el estado de todos los trabajos de una misma solicitud. |
| `GET /jobs/{jobId}/{name}` | Descarga un resultado. |

```bash cURL theme={null}
# Enviar
curl -s -F files=@invoice.pdf -F outputTypes=json http://localhost:8080/parse
# {"jobs":[{"file":"invoice.pdf","jobId":"e08f769f-..."}]}

# Consultar
curl -s http://localhost:8080/jobs/e08f769f-...
# {"status":"successful","files":["e08f769f-.../invoice.json"],...}

# Descargar
curl -OJ http://localhost:8080/jobs/e08f769f-.../invoice.json
```

```python Python theme={null}
import pathlib, time

import requests

FP = "http://localhost:8080"

with open("invoice.pdf", "rb") as f:
    r = requests.post(f"{FP}/parse", files={"files": f}, data={"outputTypes": ["json"]})
r.raise_for_status()
job = r.json()["jobs"][0]["jobId"]

while (status := requests.get(f"{FP}/jobs/{job}").json())["status"] in ("pending", "in-progress"):
    time.sleep(3)
if status["status"] != "successful":
    raise RuntimeError(status["failure"])

for entry in status["files"]:
    r = requests.get(f"{FP}/jobs/{entry}")
    r.raise_for_status()
    pathlib.Path(pathlib.Path(entry).name).write_bytes(r.content)
```

## POST /parse

Una solicitud `multipart/form-data`.

| Campo | Obligatorio | Valor |
| - | - | - |
| `files` | Sí | Un PDF o una imagen, como PNG, JPEG o TIFF. Repita el campo para enviar varios archivos. |
| `outputTypes` | Sí | `doclang`, `json`, `xml` o `txt`. Repita el campo para indicar varios. Consulte [Formatos de salida](https://docs.abbyy.com/es/fine-parser/enterprise/basics/output-formats.md). |
| `baseProfile` | No | Un perfil de reconocimiento predefinido. Requiere `userIni`. |
| `userIni` | No | Un perfil de usuario, como campo o como archivo. Requiere `baseProfile`. Consulte [Configuración de reconocimiento](https://docs.abbyy.com/es/fine-parser/enterprise/basics/recognition-settings.md#per-request-profiles). |

La respuesta es `202 Accepted`, con un ID de trabajo por cada archivo. Si la solicitud incluye varios archivos, también devuelve un `batchId`. Los archivos que no se hayan podido aceptar aparecen en `errors`.

```json theme={null}
{"batchId":"c5a6dd90-...","jobs":[{"file":"a.pdf","jobId":"c2f11bb8-..."},{"file":"b.pdf","jobId":"2ab42551-..."}]}
```

`POST /parse` no es idempotente. Si vuelve a enviar la solicitud después de que se agote el tiempo de espera, se pueden crear trabajos duplicados.

## GET /jobs/{jobId}

| Campo | Significado |
| - | - |
| `status` | `pending`, `in-progress`, `successful` o `failed`. |
| `files` | Si es `successful`: una entrada por resultado, con el formato `{jobId}/{name}`. |
| `durationSeconds` | Si es `successful`: segundos transcurridos desde el envío hasta la finalización. |
| `failure` | Si es `failed`: `{"reason": ..., "message": ...}`. |

El estado de un trabajo finalizado se conserva durante `orchestrator.jobRetentionSeconds` (24 horas de forma predeterminada). Pasado ese plazo, este endpoint devuelve `404`, aunque los resultados todavía se pueden descargar.

| `reason` | Qué hacer |
| - | - |
| `profile-rejected` | Corrija el ajuste del perfil que se indica en el mensaje. |
| `ingest-failed` | Compruebe que el archivo se puede abrir. Es posible que esté dañado o que tenga un formato no compatible. |
| `never-started` | Vuelva a enviarlo. |
| `recognition-failed` | Envíelo una vez más. Si vuelve a fallar, es probable que el documento sea ilegible. |
| `finalize-failed` | Compruebe el volumen de salida y vuelva a enviarlo. |
| `timed-out` | Consulte [Tamaño de unidad y concesiones](https://docs.abbyy.com/es/fine-parser/enterprise/concepts/scaling.md#unit-size-and-leases). |

## GET /batch/{batchId}

Devuelve el `status` de toda la solicitud y `jobs`, que incluye el `jobId`, el `file` y el `status` de cada trabajo con la misma estructura que `GET /jobs/{jobId}`. Un lote solo es `successful` cuando todos sus trabajos lo son.

## GET /jobs/{jobId}/{name}

Descarga un resultado. Cada entrada de `files` ya tiene el formato `{jobId}/{name}`, así que basta con añadirla a continuación de `/jobs/`. La descarga no elimina nada.

## Errores

Los errores se devuelven en formato JSON: `{"error":{"code":"...","message":"..."}}`. Use el valor de `code` para determinar cómo gestionarlos.

| `code` | HTTP | Significado |
| - | - | - |
| `invalid_request` | 400 | Falta un campo o no es válido. El mensaje indica cuál. |
| `no_files_accepted` | 400 | No se pudo aceptar ningún archivo. `errors` indica el motivo para cada uno. |
| `not_found` | 404 | El trabajo, lote, archivo o ruta no existe. |
| `method_not_allowed` | 405 | Método HTTP incorrecto. |
| `upload_too_large` | 413 | La solicitud supera el tamaño de `ingest.maxUploadSize`. |
| `orchestrator_error` | 502 | Se produjo un error interno inesperado. |
| `orchestrator_unavailable` | 503 | No disponible temporalmente. Vuelva a intentarlo cuando transcurran los segundos indicados en `Retry-After`. |

## Límites

| Límite | Configuración |
| - | - |
| Tamaño de la solicitud, sumando todos los archivos | `ingest.maxUploadSize` |
| Partes de formulario por solicitud, sumando archivos y `outputTypes` | 5000 |
| Tamaño de `userIni` | 1 MiB |

## CORS

Para llamar a la API desde un navegador en otro origen, indique los orígenes en `ingest.cors.allowedOrigins`, separados por comas. De forma predeterminada, no se permite ningún origen.
