# API de herramient.as

API REST para usar las herramientas de herramient.as desde tus aplicaciones y agentes.

- Página: https://herramient.as/api-docs
- Especificación OpenAPI 3.1: https://herramient.as/api/v1/openapi.json (también en https://herramient.as/api/v1/openapi.yaml)

## Autenticación

Cada petición lleva una clave en la cabecera `Authorization: Bearer htas_...`. Las claves se crean en el panel, tras entrar con Google: https://herramient.as/dashboard

## Límites

Plan gratuito: 20 peticiones por minuto y 500 por hora por clave, y 2.000 unidades de cuota por periodo. Al superar el límite la API responde `429 Too Many Requests` con una cabecera `Retry-After`. Los ficheros generados se borran en 24 horas.

Además de los errores de cada endpoint, cualquier petición puede devolver `401` (clave ausente o no válida) y `429` (límite superado).

## Endpoints

### Amortización anticipada: reducir cuota o plazo

https://herramient.as/amortizacion-anticipada-hipoteca

#### `POST /api/v1/tools/mortgage/early_repayment`

Compara reducir cuota con reducir plazo al amortizar anticipadamente

Devuelve las dos salidas una al lado de la otra: misma fecha de fin con cuota más baja, o misma cuota terminando antes. Reducir plazo ahorra siempre al menos lo mismo que reducir cuota, porque deja menos capital pendiente durante menos tiempo; eso es una identidad aritmética, no un hallazgo, y el campo verdict solo cambia a not_worth_it cuando la comisión se come el ahorro. La comisión se cobra sobre el importe amortizado y se suma al desembolso: el importe completo reduce capital y la comisión es dinero perdido, que es como la cobran los bancos. Los ahorros vienen ya netos de comisión.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `balance` | number | sí | Capital pendiente en euros (Ejemplo: `150000`) |
| `rate` | number | sí | TIN actual en porcentaje (Ejemplo: `3.0`) |
| `remaining_months` | integer | sí | Cuotas que quedan por pagar (Ejemplo: `200`) |
| `amount` | number | sí | Importe a amortizar, no mayor que el capital pendiente (Ejemplo: `20000`) |
| `commission_percent` | number | no | Comisión por amortización anticipada, en porcentaje sobre el importe. Por defecto 0 (Ejemplo: `0.15`) |
| `investment_return` | number | no | Rentabilidad anual con la que comparar invertir ese dinero en lugar de amortizar. Bruta y antes de impuestos (Ejemplo: `4.0`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `amount` | number | Importe amortizado |
| `commission` | number | Comisión en euros |
| `total_outlay` | number | Lo que sale de tu bolsillo: importe más comisión |
| `current` | object | Situación actual: monthly_payment, months y total_interest |
| `reduce_payment` | object | Reducir cuota: lo anterior más net_saving y monthly_saving |
| `reduce_term` | object | Reducir plazo: lo anterior más net_saving y months_saved |
| `extra_saving_from_term` | number | Cuánto más ahorras reduciendo plazo en lugar de cuota |
| `investment` | object | Solo con investment_return: final_value, gross_gain y beats_repayment. Bruto y antes de impuestos |
| `verdict` | string | reduce_term, o not_worth_it si la comisión anula el ahorro |
| `message` | string | El veredicto en lenguaje llano |
| `disclaimer` | string | Aviso de que el cálculo es orientativo |

**Errores**

- `400`: Capital pendiente, plazo o importe no válidos, o importe mayor que el capital pendiente

```bash
curl -X POST https://herramient.as/api/v1/tools/mortgage/early_repayment \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"balance":150000,"rate":3.0,"remaining_months":200,"amount":20000,"commission_percent":0.15,"investment_return":4.0}'
```

### Calculadora de hipoteca

https://herramient.as/calculadora-hipoteca

#### `POST /api/v1/tools/mortgage/schedule`

Calcula la cuota y el cuadro de amortización de una hipoteca

Sistema francés, con i igual al TIN dividido entre doce, que es la convención española. El redondeo es a céntimos en cada periodo y la última cuota absorbe el resto, así que el capital amortizado suma exactamente el prestado. En las hipotecas variables y mixtas la cuota se recalcula sobre el capital pendiente y el plazo restante en cada revisión, nunca sobre el capital inicial. euribor_shock permite montar escenarios de estrés: los puntos se suman al Euríbor a partir de la primera revisión, no desde el primer mes, porque el tipo inicial está fijado por contrato hasta entonces. El cuadro completo solo se devuelve si pides include_schedule.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `capital` | number | sí | Importe del préstamo en euros (Ejemplo: `200000`) |
| `years` | integer | sí | Plazo en años, de 1 a 50 (Ejemplo: `25`) |
| `type` | string | no | Tipo de hipoteca. Por defecto fixed (Ejemplo: `fixed`) |
| `rate` | number | no | TIN en porcentaje. Obligatorio en fixed, y es el tramo fijo en mixed (Ejemplo: `3.0`) |
| `euribor` | number | no | Euríbor de referencia en porcentaje, para el tramo variable (Ejemplo: `2.143`) |
| `differential` | number | no | Diferencial sobre el Euríbor en puntos (Ejemplo: `0.9`) |
| `revision_months` | integer | no | Cada cuántos meses se revisa el tipo. Por defecto 12 (Ejemplo: `12`) |
| `fixed_years` | integer | no | Años del tramo fijo, solo en mixed (Ejemplo: `10`) |
| `euribor_shock` | number | no | Puntos que se suman al Euríbor desde la primera revisión (Ejemplo: `1`) |
| `include_schedule` | boolean | no | Devuelve el cuadro mes a mes. Por defecto false (Ejemplo: `false`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `type` | string | Tipo de hipoteca calculada |
| `months` | integer | Número total de cuotas |
| `capital` | number | Capital prestado, en euros |
| `monthly_payment` | number | Primera cuota mensual, en euros |
| `total_interest` | number | Intereses totales a lo largo de la vida del préstamo |
| `total_paid` | number | Capital más intereses |
| `initial_rate` | number | Tipo anual aplicado a la primera cuota |
| `nominal_annual_rate` | number | TIN. El mismo valor que initial_rate, nombrado explícitamente |
| `effective_annual_rate` | number | Efecto de la capitalización mensual sobre el TIN: (1+TIN/12)^12−1. No es la TAE: no incluye gastos ni comisiones |
| `revisions` | array | Meses en los que cambia la cuota, con month, rate y monthly_payment |
| `schedule` | array | Solo con include_schedule: month, rate, payment, interest, principal y balance de cada cuota |
| `disclaimer` | string | Aviso de que el cálculo es orientativo |

**Errores**

- `400`: Capital, plazo o tipo de hipoteca no válidos

```bash
curl -X POST https://herramient.as/api/v1/tools/mortgage/schedule \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"capital":200000,"years":25,"type":"fixed","rate":3.0,"euribor":2.143,"differential":0.9,"revision_months":12,"fixed_years":10,"euribor_shock":1,"include_schedule":false}'
```

### Calculadora de ITP

https://herramient.as/calculadora-itp

#### `POST /api/v1/tools/property/itp`

Calcula solo el impuesto de adquisición de una vivienda

El mismo motor que purchase_costs, recortado al impuesto: devuelve la base imponible, el tipo, la cuota y los datos de presentación, sin honorarios ni efectivo necesario. En segunda mano es ITP; en obra nueva, el impuesto indirecto que corresponda al territorio —IVA en la península y Baleares, IGIC en Canarias, IPSI en Ceuta y Melilla— más el AJD. Acepta los mismos parámetros de perfil que purchase_costs.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `jurisdiction` | string | sí | Código de la jurisdicción fiscal (Ejemplo: `cataluna`) |
| `price` | number | sí | Precio de compra en euros (Ejemplo: `300000`) |
| `condition` | string | no | Por defecto second_hand (Ejemplo: `second_hand`) |
| `reference_value` | number | no | Valor de referencia del Catastro en euros (Ejemplo: `330000`) |
| `accrual_date` | string | no | Fecha de devengo en AAAA-MM-DD (Ejemplo: `2026-10-05`) |
| `use` | string | no | Uso de la vivienda (Ejemplo: `habitual`) |
| `age` | integer | no | Edad de quien compra (Ejemplo: `30`) |
| `irpf_qualifying_base` | number | no | Base de IRPF a efectos del tipo reducido (Ejemplo: `25000`) |
| `family_category` | string | no | Situación familiar (Ejemplo: `numerosa`) |
| `disability_percentage` | number | no | Grado de discapacidad en porcentaje (Ejemplo: `65`) |
| `municipality` | string | no | Municipio del inmueble (Ejemplo: `Tremp`) |
| `large_holder` | boolean | no | Si quien compra es gran tenedor (Ejemplo: `false`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `available` | boolean | false si no hay regla verificada para esa jurisdicción y fecha |
| `jurisdiction` | object | code, name, type, indirect_tax_regime y foral |
| `taxable_base` | object | La base y de cuál de las tres magnitudes sale |
| `acquisition_tax` | object | tax, rule, rate, base, quota, verified_at y sources |
| `ajd` | object | Solo en obra nueva |
| `candidates` | array | Tipos reducidos que podrían aplicar, con lo que falta por saber |
| `trace` | array | Cada regla considerada con su veredicto |
| `filing` | object | model, deadline, who, where y una nota: modelo 600 y un mes desde la escritura |
| `warnings` | array | Avisos con code y message |
| `disclaimer` | string | Aviso de que el cálculo es orientativo |

**Errores**

- `400`: Jurisdicción desconocida, precio no válido o fecha mal formada

```bash
curl -X POST https://herramient.as/api/v1/tools/property/itp \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"jurisdiction":"cataluna","price":300000,"condition":"second_hand","reference_value":330000,"accrual_date":"2026-10-05","use":"habitual","age":30,"irpf_qualifying_base":25000,"family_category":"numerosa","disability_percentage":65,"municipality":"Tremp","large_holder":false}'
```

#### `POST /api/v1/tools/property/comparison`

Compara el mismo impuesto en todas las jurisdicciones con datos verificados

La misma compra y el mismo perfil en cada comunidad, territorio foral y ciudad autónoma de la que tengamos el tipo verificado, ordenadas de menos a más impuesto. Las que no aparecen van en unavailable: es un hueco en nuestros datos, no un territorio sin impuesto.

Consume 2 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `price` | number | sí | Precio de compra en euros (Ejemplo: `300000`) |
| `condition` | string | no | Por defecto second_hand (Ejemplo: `second_hand`) |
| `reference_value` | number | no | Valor de referencia del Catastro en euros (Ejemplo: `330000`) |
| `accrual_date` | string | no | Fecha de devengo en AAAA-MM-DD (Ejemplo: `2026-10-05`) |
| `use` | string | no | Uso de la vivienda (Ejemplo: `habitual`) |
| `age` | integer | no | Edad de quien compra (Ejemplo: `30`) |
| `irpf_qualifying_base` | number | no | Base de IRPF a efectos del tipo reducido (Ejemplo: `25000`) |
| `large_holder` | boolean | no | Si quien compra es gran tenedor (Ejemplo: `false`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `price` | number | El precio comparado |
| `condition` | string | Segunda mano u obra nueva |
| `jurisdictions` | array | Las jurisdicciones con datos, ordenadas por cuota ascendente, cada una con su tax y su ajd |
| `unavailable` | array | Las jurisdicciones sin tipo verificado todavía |
| `unavailable_note` | string | Por qué faltan: hueco en los datos, no ausencia de impuesto |
| `disclaimer` | string | Aviso de que el cálculo es orientativo |

**Errores**

- `400`: Precio no válido o fecha mal formada

```bash
curl -X POST https://herramient.as/api/v1/tools/property/comparison \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"price":300000,"condition":"second_hand","reference_value":330000,"accrual_date":"2026-10-05","use":"habitual","age":30,"irpf_qualifying_base":25000,"large_holder":false}'
```

### Comparar Dos Textos

https://herramient.as/comparar-textos

#### `POST /api/v1/tools/text/diff`

Compara dos textos y devuelve qué ha cambiado

Devuelve los tramos iguales, eliminados y añadidos, por líneas. El único endpoint de esta familia que no toca ficheros, así que no tiene ni límite de subida ni el 503 de los demás. Las tres opciones de normalización deciden qué cuenta como un cambio; ignore_whitespace viene activada porque es lo que se quiere casi siempre al comparar texto pegado de un PDF.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `left` | string | sí | Texto original. Máximo 200.000 caracteres. Puede ser una cadena vacía (Ejemplo: `plazo de treinta días`) |
| `right` | string | sí | Texto modificado. Máximo 200.000 caracteres. Puede ser una cadena vacía (Ejemplo: `plazo de quince días`) |
| `ignore_case` | boolean | no | Tratar mayúsculas y minúsculas como iguales. Por defecto false |
| `ignore_whitespace` | boolean | no | Colapsar espacios repetidos y recortar los extremos. Por defecto **true** |
| `ignore_punctuation` | boolean | no | Descartar signos de puntuación. Por defecto false |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `identical` | boolean | Si no hay ninguna diferencia tras aplicar la normalización |
| `stats` | object | added, removed y equal: número de líneas de cada clase |
| `parts` | array | Los tramos en orden. Cada uno trae type (equal, added o removed) y las líneas en left y right |

**Errores**

- `400`: Falta left o right, no son texto, o superan los 200.000 caracteres

```bash
curl -X POST https://herramient.as/api/v1/tools/text/diff \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"left":"plazo de treinta días","right":"plazo de quince días"}'
```

### Comprimir Imagen

https://herramient.as/comprimir-imagen

#### `POST /api/v1/tools/images/compress`

Recomprime una imagen para que pese menos

Responde con **los bytes de la imagen**, no con JSON ni con una URL: nada se almacena en el servidor. Las medidas de salida viajan en las cabeceras X-Image-Width, X-Image-Height y X-Image-Bytes. Se eliminan todos los metadatos, incluida la ubicación GPS. Reducir el ancho con max_width es lo que de verdad aligera una foto de móvil.

Consume 3 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `file` | file | sí | La imagen, como multipart/form-data. Máximo 25 MB |
| `format` | string | no | Formato de salida. Por defecto jpeg (Ejemplo: `webp`) |
| `quality` | integer | no | Calidad de 1 a 100. Por defecto 80. No aplica a png (Ejemplo: `80`) |
| `max_width` | integer | no | Ancho máximo en píxeles. No agranda una imagen más pequeña (Ejemplo: `1920`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `(binario)` | string | Los bytes de la imagen. El Content-Type es image/<format> |

**Errores**

- `400`: Falta el fichero, supera 25 MB, o format/quality/max_width no son válidos
- `422`: El fichero está dañado o no es una imagen que sepamos decodificar
- `503`: El servicio de procesamiento no está disponible. Reintenta según Retry-After

```bash
curl -X POST https://herramient.as/api/v1/tools/images/compress \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"format":"webp","quality":80,"max_width":1920}'
```

### Comprobar CIF de empresa

https://herramient.as/comprobar-cif

#### `POST /api/v1/tools/cif/validate`

Valida el CIF de una entidad y describe su naturaleza jurídica

El carácter de control es un dígito para las claves A, B, C, D, E, F, G, H, J, U y V, y una letra para N, P, Q, R, S y W. Ninguna clave admite las dos formas.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `cif` | string | sí | CIF de la entidad (Ejemplo: `A28015865`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `valid` | boolean | Si el CIF es válido |
| `type` | string | CIF, o null si el formato no se reconoce |
| `checksum_valid` | boolean | Si el carácter de control cuadra con los dígitos |
| `normalized` | string | Entrada en mayúsculas y sin separadores |
| `details` | object | key, entity_key, control_type (letter o digit), expected_control y given_control |
| `message` | string | Mensaje legible del resultado |

**Errores**

- `400`: El CIF está vacío

```bash
curl -X POST https://herramient.as/api/v1/tools/cif/validate \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"cif":"A28015865"}'
```

### Comprobar DNI, NIF y NIE

https://herramient.as/dni-nif-validator

#### `POST /api/v1/tools/dni/validate`

Valida un DNI, NIE o NIF

Detecta automáticamente el tipo de documento. Por compatibilidad histórica los identificadores de entidad se devuelven con type NIF; usa /api/v1/tools/identifier/detect si prefieres el tipo preciso y el desglose.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `id` | string | sí | DNI, NIE o NIF a validar (Ejemplo: `12345678Z`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `valid` | boolean | Si el documento es válido |
| `type` | string | Tipo detectado, null si no se reconoce |
| `checksum_valid` | boolean | Si la letra o carácter de control cuadra |
| `message` | string | Mensaje legible del resultado |

**Errores**

- `400`: El documento está vacío

```bash
curl -X POST https://herramient.as/api/v1/tools/dni/validate \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"id":"12345678Z"}'
```

### Comprobar número de la Seguridad Social

https://herramient.as/comprobar-numero-seguridad-social

#### `POST /api/v1/tools/naf/validate`

Valida un número de afiliación a la Seguridad Social (NAF o NUSS)

Los dos dígitos de control son el resto de dividir entre 97 los diez primeros dígitos leídos en el orden en que se escriben, provincia primero. No hay paso de 98 menos el resto como en IBAN, así que el control va de 00 a 96. Se comprueba además que la provincia exista: los códigos 54 a 65 no están asignados, de modo que un número puede cuadrar aritméticamente y ser imposible.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `naf` | string | sí | Número de 12 dígitos. Se admiten espacios y guiones (Ejemplo: `081304526390`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `valid` | boolean | Si el número es válido: control correcto y provincia existente |
| `type` | string | NAF, o null si el formato no se reconoce |
| `checksum_valid` | boolean | Si los dígitos de control cuadran, con independencia de la provincia |
| `normalized` | string | Entrada sin separadores |
| `details` | object | province, province_key, province_name, serial, control y expected_control |
| `message` | string | Mensaje legible del resultado |

**Errores**

- `400`: El número de la Seguridad Social está vacío

```bash
curl -X POST https://herramient.as/api/v1/tools/naf/validate \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"naf":"081304526390"}'
```

### Comprobar referencia catastral

https://herramient.as/comprobar-referencia-catastral

#### `POST /api/v1/tools/catastral/lookup`

Consulta una referencia catastral en el Catastro

Resuelve una referencia catastral contra los servicios web libres de la Dirección General del Catastro y devuelve los datos no protegidos del inmueble: dirección, uso, superficie construida, año de construcción, el desglose de construcciones y las coordenadas de la parcela. Añade además la superficie de la parcela, lo edificado sobre ella y el plano catastral, tomados de los servicios INSPIRE; esas tres superficies miden cosas distintas y no deben sumarse. No se consultan datos de titularidad, que requieren certificado digital. Admite la referencia completa de 20 caracteres y la parcela de 14. Si los caracteres de control no cuadran, el Catastro lo indica y la respuesta llega con found a false.

Consume 5 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `reference` | string | sí | Referencia catastral de 20 caracteres, o parcela de 14. Se admiten espacios (Ejemplo: `9872023VH5797S0001WX`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `found` | boolean | Si el Catastro ha resuelto la referencia |
| `reference` | string | La referencia completa de 20 caracteres tal y como la devuelve el Catastro |
| `nature` | string | Urbana o rústica, según el Catastro |
| `address` | string | Localización completa del inmueble |
| `province` | string | Provincia |
| `municipality` | string | Municipio |
| `postal_code` | string | Código postal |
| `use` | string | Uso principal, por ejemplo Residencial o Agrario |
| `built_area_m2` | integer | Superficie construida del inmueble, en metros cuadrados |
| `year_built` | integer | Año de construcción, null si el Catastro no lo recoge |
| `participation_coefficient` | number | Coeficiente de participación en la finca |
| `constructions` | array | Desglose por unidad: use, block, floor, door y area_m2 |
| `plot_area_m2` | integer | Superficie de la parcela en metros cuadrados, según la cartografía INSPIRE. No es la del inmueble |
| `built_on_plot_m2` | integer | Suma de la superficie construida de todos los edificios de la parcela. En un piso es mucho mayor que built_area_m2 |
| `buildings_count` | integer | Número de edificios levantados sobre la parcela |
| `dwellings` | integer | Número de viviendas en la parcela |
| `plan` | object | Plano catastral: url del PNG, width, height, bbox (min_lon, min_lat, max_lon, max_lat) y outline con el contorno de la parcela en pares [lat, lon] |
| `coordinates` | object | lat, lon y srs del centroide de la parcela. null si el Catastro no las sirve |
| `catastro_url` | string | Enlace a la ficha oficial en la Sede Electrónica del Catastro |
| `cartography_url` | string | Enlace a la parcela en la cartografía catastral oficial |
| `message` | string | Mensaje legible del resultado |

**Errores**

- `400`: La referencia catastral está vacía
- `503`: El servicio del Catastro no está disponible

```bash
curl -X POST https://herramient.as/api/v1/tools/catastral/lookup \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"reference":"9872023VH5797S0001WX"}'
```

#### `POST /api/v1/tools/catastral/search`

Busca la referencia catastral a partir de una dirección

El camino inverso: de dirección a referencia catastral. Un número de calle suele agrupar muchos inmuebles, así que la respuesta habitual es una lista en properties con la referencia y la planta y puerta de cada uno; eso no es un error. Añadiendo planta y puerta la respuesta se concreta en un solo inmueble, con found a true y todos sus datos. Los nombres de provincia, municipio y calle tienen que coincidir con la grafía del Catastro: el endpoint libre no permite listar su callejero, así que cuando algo no cuadra devolvemos el motivo exacto del Catastro en catastro_error.

Consume 5 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `province` | string | sí | Provincia, en la grafía del Catastro (Ejemplo: `MADRID`) |
| `municipality` | string | sí | Municipio (Ejemplo: `MADRID`) |
| `street_type` | string | no | Sigla de la vía: CL, AV, PZ, CR... (Ejemplo: `CL`) |
| `street` | string | sí | Nombre de la vía, sin la sigla (Ejemplo: `ARTURO SORIA`) |
| `number` | string | sí | Número de policía (Ejemplo: `100`) |
| `block` | string | no | Bloque (Ejemplo: ``) |
| `stairway` | string | no | Escalera (Ejemplo: ``) |
| `floor` | string | no | Planta. Acota la búsqueda a un inmueble (Ejemplo: `01`) |
| `door` | string | no | Puerta. Acota la búsqueda a un inmueble (Ejemplo: `A`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `found` | boolean | true solo cuando la dirección identifica un único inmueble |
| `properties` | array | Cuando la dirección agrupa varios inmuebles: reference y address de cada uno |
| `reference` | string | La referencia completa de 20 caracteres, si hay un único inmueble |
| `address` | string | Localización completa, con planta y puerta |
| `built_area_m2` | integer | Superficie construida en metros cuadrados |
| `year_built` | integer | Año de construcción |
| `use` | string | Uso principal |
| `coordinates` | object | lat, lon y srs del centroide de la parcela |
| `catastro_url` | string | Enlace a la ficha oficial en la Sede Electrónica |
| `cartography_url` | string | Enlace a la parcela en la cartografía catastral oficial |
| `catastro_error` | object | code y description del Catastro cuando rechaza la dirección |
| `message` | string | Mensaje legible del resultado |

**Errores**

- `400`: Faltan provincia, municipio, calle o número
- `503`: El servicio del Catastro no está disponible

```bash
curl -X POST https://herramient.as/api/v1/tools/catastral/search \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"province":"MADRID","municipality":"MADRID","street_type":"CL","street":"ARTURO SORIA","number":"100","block":"","stairway":"","floor":"01","door":"A"}'
```

#### `GET /api/v1/tools/catastral/municipios`

Lista los municipios de una provincia con la grafía del Catastro

Pensado para montar un selector antes de llamar a /catastral/search. Los nombres son los que usa el Catastro, que no siempre coinciden con los del INE. Devuelve una lista vacía para Álava, Gipuzkoa, Bizkaia y Navarra, que tienen catastro foral propio y no están en este servicio.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `province` | string | sí | Provincia, en la grafía del Catastro (Ejemplo: `MADRID`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `municipalities` | array | Nombres de municipio, alfabéticamente |

```bash
curl -X GET https://herramient.as/api/v1/tools/catastral/municipios \
  -H "Authorization: Bearer htas_tu_clave_api"
```

#### `GET /api/v1/tools/catastral/vias`

Busca vías de un municipio con la grafía del Catastro

El Catastro guarda los nombres de vía normalizados, sin tildes y a veces con el orden invertido, de modo que no se pueden deducir del nombre de la placa. Este endpoint los devuelve tal cual, para usarlos en el parámetro street de /catastral/search. Hacen falta al menos 3 caracteres en q y se devuelven como máximo 50 resultados.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `province` | string | sí | Provincia (Ejemplo: `MADRID`) |
| `municipality` | string | sí | Municipio (Ejemplo: `MADRID`) |
| `q` | string | sí | Parte del nombre de la vía, mínimo 3 caracteres (Ejemplo: `ALCAL`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `streets` | array | street_type (sigla), name (el valor para search) y label (legible) |

```bash
curl -X GET https://herramient.as/api/v1/tools/catastral/vias \
  -H "Authorization: Bearer htas_tu_clave_api"
```

#### `GET /api/v1/tools/catastral/plano`

Devuelve el plano catastral de una parcela en PNG

Responde con una imagen PNG, no con JSON: es la cartografía catastral oficial recortada sobre la parcela de la referencia. Hacemos de intermediarios porque el servicio WMS del Catastro no envía cabeceras CORS y porque el encuadre hay que calcularlo a partir de la geometría de la parcela. La atribución a la Dirección General del Catastro va impresa en la propia imagen. Devuelve 404 cuando la parcela no tiene cartografía, lo que incluye Álava, Gipuzkoa, Bizkaia y Navarra. Para situar el plano o dibujar encima, el bbox y el contorno vienen en el campo plan de /catastral/lookup.

Consume 5 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `reference` | string | sí | Referencia catastral de 20 caracteres, o parcela de 14 (Ejemplo: `9872023VH5797S0001WX`) |
| `tamano` | string | no | Tamaño de la imagen: card (640x420) o wide (1024x420). Por defecto card (Ejemplo: `card`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `(binario)` | string | La imagen PNG. El Content-Type es image/png |

**Errores**

- `404`: La parcela no tiene cartografía disponible

```bash
curl -X GET https://herramient.as/api/v1/tools/catastral/plano \
  -H "Authorization: Bearer htas_tu_clave_api"
```

### Conversor HEX ↔ RGB

https://herramient.as/hex-rgb

#### `POST /api/v1/tools/hex_to_rgb`

Convierte un color HEX a RGB

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `hex` | string | sí | Color en formato #RRGGBB (Ejemplo: `#FF5733`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `r` | integer | Componente rojo, 0-255 |
| `g` | integer | Componente verde, 0-255 |
| `b` | integer | Componente azul, 0-255 |
| `hex` | string | El color normalizado en mayúsculas |

**Errores**

- `400`: Formato HEX inválido

```bash
curl -X POST https://herramient.as/api/v1/tools/hex_to_rgb \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"hex":"#FF5733"}'
```

#### `POST /api/v1/tools/rgb_to_hex`

Convierte un color RGB a HEX

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `r` | integer | sí | Componente rojo, 0-255 (Ejemplo: `255`) |
| `g` | integer | sí | Componente verde, 0-255 (Ejemplo: `87`) |
| `b` | integer | sí | Componente azul, 0-255 (Ejemplo: `51`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `hex` | string | Color en formato #RRGGBB |
| `r` | integer | Componente rojo recibido |
| `g` | integer | Componente verde recibido |
| `b` | integer | Componente azul recibido |

**Errores**

- `400`: Valores RGB fuera del rango 0-255

```bash
curl -X POST https://herramient.as/api/v1/tools/rgb_to_hex \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"r":255,"g":87,"b":51}'
```

### Convertir CCC a IBAN

https://herramient.as/convertir-ccc-a-iban

#### `POST /api/v1/tools/ccc/to_iban`

Convierte un CCC de 20 dígitos en su IBAN

Valida primero los dos dígitos de control internos del CCC. Si no cuadran no se devuelve IBAN, porque construirlo sobre una cuenta incorrecta produciría un IBAN sintácticamente válido que apunta a otra cuenta.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `ccc` | string | sí | Código Cuenta Cliente de 20 dígitos. Se admiten espacios y guiones (Ejemplo: `21000418450200051332`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `valid` | boolean | Si el CCC es válido |
| `type` | string | CCC, o null si el formato no se reconoce |
| `checksum_valid` | boolean | Si los dígitos de control cuadran |
| `normalized` | string | Entrada sin separadores |
| `details` | object | entidad, oficina, control, cuenta, expected_control e iban (electronic y formatted), null si no es válido |
| `message` | string | Mensaje legible del resultado |

**Errores**

- `400`: El CCC está vacío

```bash
curl -X POST https://herramient.as/api/v1/tools/ccc/to_iban \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"ccc":"21000418450200051332"}'
```

### ¿Cuál es mi IP?

https://herramient.as/cual-es-mi-ip

#### `GET /api/v1/tools/ip`

Devuelve información sobre la IP del cliente

Detecta la IP real de quien llama, resolviendo correctamente proxies, CDN y balanceadores. No acepta una IP arbitraria como parámetro.

Consume 1 unidad de cuota.

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `ipv4` | string | Dirección IPv4 detectada, null si no hay |
| `ipv6` | string | Dirección IPv6 detectada, null si no hay |
| `country` | string | País |
| `country_code` | string | Código ISO del país |
| `city` | string | Ciudad aproximada |
| `region` | string | Comunidad autónoma o región |
| `postal_code` | string | Código postal aproximado |
| `latitude` | number | Latitud aproximada |
| `longitude` | number | Longitud aproximada |
| `asn` | string | Número de sistema autónomo |
| `organization` | string | Organización propietaria del rango |
| `isp` | string | Proveedor de internet |
| `connection_type` | string | Tipo de conexión detectado |
| `is_vpn_proxy` | boolean | Si parece una VPN, proxy o hosting |
| `timestamp` | string | Momento de la consulta |

**Errores**

- `500`: Error al resolver la geolocalización

```bash
curl -X GET https://herramient.as/api/v1/tools/ip \
  -H "Authorization: Bearer htas_tu_clave_api"
```

### Extraer Texto de PDF o Imagen

https://herramient.as/extraer-texto-pdf-imagen

#### `POST /api/v1/tools/documents/extract_text`

Extrae la capa de texto de un PDF

Lee el texto que el PDF ya lleva dentro, que es el caso de casi cualquier factura, nómina o extracto generado por ordenador: es inmediato y exacto. **No hace OCR.** Un PDF escaneado son imágenes sin capa de texto y la respuesta lo indica con scanned=true; para esos, la página de la herramienta reconoce el texto en el navegador. El reconocimiento en servidor llegará con su propia celda, porque son minutos de CPU por documento y pide un trabajo asíncrono, no una llamada sincrónica. El fichero no se almacena.

Consume 10 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `file` | file | sí | El PDF, como multipart/form-data. Máximo 25 MB |
| `max_pages` | integer | no | Cuántas páginas leer, de 1 a 200. Por defecto 200 (Ejemplo: `50`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `text` | string | El texto extraído, con las páginas separadas por una línea en blanco |
| `pages` | integer | Páginas que tiene el documento |
| `pages_read` | integer | Páginas leídas de verdad, que difiere de pages si se aplicó max_pages |
| `truncated` | boolean | Si quedaron páginas sin leer por el límite |
| `scanned` | boolean | True si no hay ni una letra: el PDF es un escaneo y necesita OCR |
| `characters` | integer | Caracteres del texto extraído |
| `words` | integer | Palabras del texto extraído |

**Errores**

- `400`: Falta el fichero, no es un fichero, supera 25 MB, o max_pages fuera de rango
- `422`: El PDF está dañado, o protegido con contraseña, o no es un PDF
- `503`: El servicio de procesamiento no está disponible. Reintenta según Retry-After

```bash
curl -X POST https://herramient.as/api/v1/tools/documents/extract_text \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"max_pages":50}'
```

### Gastos de compraventa de vivienda

https://herramient.as/gastos-compraventa-vivienda

#### `POST /api/v1/tools/property/purchase_costs`

Calcula el efectivo necesario para comprar una vivienda en España

Impuesto de adquisición, honorarios y entrada, con la asignación de quién paga qué. Tres cosas que lo diferencian de multiplicar el precio por un tipo. Primero, la base imponible no es el precio: es la mayor de tres magnitudes —valor declarado, precio pactado y valor de referencia del Catastro— según el art. 10 del texto refundido del ITPAJD. Segundo, la notaría y el registro se calculan con el arancel oficial (RD 1426/1989 y RD 1427/1989, con la rebaja del 5 % y el IVA), y se devuelven como horquilla porque el arancel es el suelo de la factura. Tercero, el AJD de la escritura de hipoteca se devuelve con payer igual a lender: lo paga el banco desde el RDL 17/2018, y no entra en el efectivo que necesitas. Una jurisdicción sin tipo verificado responde 200 con available en false y el aviso correspondiente, nunca un tipo aproximado ni una media nacional.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `jurisdiction` | string | sí | Código de la comunidad, territorio foral o ciudad autónoma. Por ejemplo cataluna, galicia, madrid, navarra, bizkaia, ceuta (Ejemplo: `cataluna`) |
| `price` | number | sí | Precio de compra en euros (Ejemplo: `300000`) |
| `condition` | string | no | Segunda mano o obra nueva. Por defecto second_hand (Ejemplo: `second_hand`) |
| `reference_value` | number | no | Valor de referencia del Catastro en euros. Si supera el precio, pasa a ser la base imponible (Ejemplo: `330000`) |
| `declared_value` | number | no | Valor declarado en la escritura, si difiere del precio. Por defecto igual al precio (Ejemplo: `300000`) |
| `consideration` | number | no | Contraprestación pactada, si difiere del valor declarado. Por defecto igual al precio (Ejemplo: `300000`) |
| `mortgage_amount` | number | no | Importe de la hipoteca en euros. Determina la entrada y añade la tasación (Ejemplo: `240000`) |
| `accrual_date` | string | no | Fecha de devengo en AAAA-MM-DD. Selecciona la versión de la regla vigente ese día, no la de hoy. Por defecto hoy (Ejemplo: `2026-10-05`) |
| `use` | string | no | Uso de la vivienda. Requisito de casi todos los tipos reducidos (Ejemplo: `habitual`) |
| `age` | integer | no | Edad de quien compra en la fecha de devengo (Ejemplo: `30`) |
| `irpf_qualifying_base` | number | no | Base imponible general más la del ahorro de la última declaración, menos el mínimo personal y familiar. No es el sueldo bruto (Ejemplo: `25000`) |
| `family_category` | string | no | Situación familiar que da derecho a tipo reducido (Ejemplo: `numerosa`) |
| `extra_children` | integer | no | Hijos por encima del mínimo de familia numerosa. Amplía el límite de renta (Ejemplo: `1`) |
| `disability_percentage` | number | no | Grado de discapacidad en porcentaje (Ejemplo: `65`) |
| `gender_violence_victim` | boolean | no | Víctima de violencia de género, para el tipo reducido correspondiente (Ejemplo: `false`) |
| `municipality` | string | no | Municipio del inmueble. Necesario para los tipos rurales (Ejemplo: `Tremp`) |
| `protected_housing` | string | no | Calificación de vivienda protegida, para el IVA superreducido (Ejemplo: `regimen_especial`) |
| `large_holder` | boolean | no | Si quien compra es gran tenedor. Un valor ausente deja el recargo sin resolver y se avisa de ello (Ejemplo: `false`) |
| `whole_building` | boolean | no | Si se adquiere un edificio residencial completo (Ejemplo: `false`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `available` | boolean | false cuando no hay regla verificada para esa jurisdicción y fecha. En ese caso no se devuelve ni base ni tipo ni totales |
| `jurisdiction` | object | code, name, type, indirect_tax_regime y foral |
| `taxable_base` | object | amount, source (declared_value, consideration o reference_value), above_price, below_reference y los componentes |
| `acquisition_tax` | object | El impuesto de adquisición: tax, rule, rate, base, quota, payer, verified_at y sources |
| `ajd` | object | AJD de la escritura de compraventa. Solo en obra nueva: en segunda mano es incompatible con el ITP (art. 31.2) |
| `mortgage_ajd` | object | AJD de la escritura de hipoteca, con payer igual a lender. No entra en el efectivo necesario |
| `fees` | array | Notaría, registro, gestoría y tasación, cada una con min, max, basis (legal_tariff o market) y bucket |
| `totals` | object | down_payment, taxes, fees_min, fees_max, cash_needed_min, cash_needed_max y paid_by_lender |
| `candidates` | array | Tipos reducidos que podrían aplicar pero que no se pueden confirmar, con lo que falta por saber y el ahorro potencial |
| `trace` | array | Cada regla considerada con su veredicto: met, unmet o unknown. Es la explicación de por qué se aplicó la que se aplicó |
| `warnings` | array | Avisos con code y message. Incluye los supuestos asumidos, como que no te aplica un recargo |
| `disclaimer` | string | Aviso de que el cálculo es orientativo |

**Errores**

- `400`: Jurisdicción desconocida, precio no válido o fecha mal formada

```bash
curl -X POST https://herramient.as/api/v1/tools/property/purchase_costs \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"jurisdiction":"cataluna","price":300000,"condition":"second_hand","reference_value":330000,"declared_value":300000,"consideration":300000,"mortgage_amount":240000,"accrual_date":"2026-10-05","use":"habitual","age":30,"irpf_qualifying_base":25000,"family_category":"numerosa","extra_children":1,"disability_percentage":65,"gender_violence_victim":false,"municipality":"Tremp","protected_housing":"regimen_especial","large_holder":false,"whole_building":false}'
```

### Generador de códigos QR

https://herramient.as/qr

#### `POST /api/v1/tools/qr/generate`

Genera un código QR con estilo, colores y logo, en PNG o SVG

Módulos cuadrados, redondeados o en puntos, esquinas a juego, colores propios y un logo opcional en el centro. Por defecto lleva debajo la firma «creado con herramient.as»; mándale caption=false para quitarla. Con logo la petición va en multipart/form-data y la corrección de errores sube al nivel H, así que cabe menos texto. El fichero se almacena 24 horas y después se elimina automáticamente; las peticiones idénticas reutilizan el fichero existente y renuevan su expiración.

Consume 3 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `content` | string | sí | Texto o URL a codificar. Máximo 2.000 caracteres (Ejemplo: `https://herramient.as`) |
| `format` | string | no | Formato de salida. Por defecto png (Ejemplo: `png`) |
| `size` | integer | no | Ancho en píxeles. Por defecto 512. Con firma, el alto es un 8 % mayor (Ejemplo: `1024`) |
| `style` | string | no | Forma de los módulos. Por defecto rounded (Ejemplo: `rounded`) |
| `eyes` | string | no | Forma de las tres esquinas. Por defecto rounded (Ejemplo: `rounded`) |
| `color` | string | no | Color del código en hexadecimal. Por defecto #111827 (Ejemplo: `#1e3a8a`) |
| `eye_color` | string | no | Color de las esquinas. Por defecto el de color (Ejemplo: `#1e3a8a`) |
| `background` | string | no | Color del fondo. Por defecto #ffffff (Ejemplo: `#ffffff`) |
| `caption` | boolean | no | Firma «creado con herramient.as» debajo del código. Por defecto true (Ejemplo: `true`) |
| `icon` | string | no | Icono para el centro del código, dibujado en vectorial. Se ignora si se manda logo (Ejemplo: `wifi`) |
| `logo` | file | no | Imagen PNG, JPG o WebP de hasta 5 MB para el centro del código, como multipart/form-data |
| `logo_size` | string | no | Tamaño del logo. Por defecto medium (Ejemplo: `medium`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `url` | string | URL de descarga del fichero generado |
| `expires_at` | string | Cuándo se eliminará el fichero |
| `content` | string | El texto codificado en el QR |
| `format` | string | Formato generado |
| `size` | integer | Ancho pedido |
| `width` | integer | Ancho de la imagen en píxeles |
| `height` | integer | Alto de la imagen en píxeles |
| `warnings` | array | Avisos que pueden impedir escanearlo, como poco contraste. No bloquean la generación |

**Errores**

- `400`: Faltan datos, el contenido no cabe en un QR, o una opción o un color no son válidos
- `422`: El logo no se ha podido leer como imagen
- `503`: El servicio que procesa el logo no está disponible; reintenta en unos segundos

```bash
curl -X POST https://herramient.as/api/v1/tools/qr/generate \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"content":"https://herramient.as","format":"png","size":1024,"style":"rounded","eyes":"rounded","color":"#1e3a8a","eye_color":"#1e3a8a","background":"#ffffff","caption":true,"icon":"wifi","logo_size":"medium"}'
```

### Generador de UUID

https://herramient.as/uuid

#### `POST /api/v1/tools/uuid`

Genera uno o varios UUID v4

Los valores de count fuera del rango 1-10 se ajustan al límite más cercano en lugar de devolver un error.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `count` | integer | no | Cuántos UUID generar, de 1 a 10. Por defecto 1 (Ejemplo: `3`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `uuids` | array | Los UUID generados |
| `count` | integer | Cuántos se han generado realmente |

```bash
curl -X POST https://herramient.as/api/v1/tools/uuid \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"count":3}'
```

### Generador DNI/NIE

https://herramient.as/dni-nif-generator

#### `POST /api/v1/tools/dni/generate`

Genera un DNI, NIE o NIF válido para pruebas

Los números son aleatorios y no corresponden a personas ni entidades reales. Úsalos solo para pruebas y desarrollo.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `type` | string | no | Tipo a generar. Por defecto DNI (Ejemplo: `DNI`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `id` | string | El identificador generado |
| `type` | string | Tipo generado |
| `checksum_valid` | boolean | Siempre true: se genera con el control correcto |

**Errores**

- `400`: Tipo inválido

```bash
curl -X POST https://herramient.as/api/v1/tools/dni/generate \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"type":"DNI"}'
```

### Lector de Códigos QR

https://herramient.as/leer-codigo-qr

#### `POST /api/v1/tools/images/decode_qr`

Lee los códigos QR o de barras de una imagen

Devuelve una lista porque una imagen puede traer varios códigos; una hoja con cuatro QR impresos es un caso corriente. Lee además códigos de barras de una dimensión (EAN-13, CODE-128 y los demás que reconoce ZBar), y el campo type dice de cuál se trata. La imagen se descodifica con libvips, así que admite JPEG, PNG, WebP, AVIF y TIFF. No se almacena. El contenido se devuelve como texto y nada más: no se interpreta, no se sigue y no se valida como URL. Un código lo escribió quien lo generó, de modo que convertirlo en algo navegable es decisión de quien llama.

Consume 2 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `file` | file | sí | La imagen con el código, como multipart/form-data. Máximo 25 MB |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `found` | boolean | Si se encontró al menos un código |
| `symbols` | array | Cada uno con data (el contenido como texto), type (QR-Code, EAN-13, CODE-128...) y quality (la confianza que da ZBar; baja en fotos borrosas) |

**Errores**

- `400`: Falta el fichero, no es un fichero o supera 25 MB
- `422`: El fichero está dañado o no es una imagen que sepamos decodificar
- `503`: El servicio de procesamiento no está disponible. Reintenta según Retry-After

```bash
curl -X POST https://herramient.as/api/v1/tools/images/decode_qr \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### QR de reseñas de Google

https://herramient.as/qr-resenas-google

#### `POST /api/v1/tools/qr/google_review`

Genera un código QR para dejar una reseña en Google

Acepta el enlace de «Pedir reseñas» del Perfil de Empresa de Google o el Place ID del negocio. Solo se admiten enlaces de dominios de Google, para que no se pueda usar para crear un código de reseñas que lleve a otra parte.

Consume 3 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `link` | string | sí | Enlace de reseñas de Google (g.page, maps.app.goo.gl, search.google.com...) o Place ID (Ejemplo: `https://g.page/r/CabcdEFGH/review`) |
| `format` | string | no | Formato de salida. Por defecto png (Ejemplo: `png`) |
| `size` | integer | no | Ancho en píxeles. Por defecto 512. Con firma, el alto es un 8 % mayor (Ejemplo: `1024`) |
| `style` | string | no | Forma de los módulos. Por defecto rounded (Ejemplo: `rounded`) |
| `eyes` | string | no | Forma de las tres esquinas. Por defecto rounded (Ejemplo: `rounded`) |
| `color` | string | no | Color del código en hexadecimal. Por defecto #111827 (Ejemplo: `#1e3a8a`) |
| `eye_color` | string | no | Color de las esquinas. Por defecto el de color (Ejemplo: `#1e3a8a`) |
| `background` | string | no | Color del fondo. Por defecto #ffffff (Ejemplo: `#ffffff`) |
| `caption` | boolean | no | Firma «creado con herramient.as» debajo del código. Por defecto true (Ejemplo: `true`) |
| `icon` | string | no | Icono para el centro del código, dibujado en vectorial. Se ignora si se manda logo (Ejemplo: `wifi`) |
| `logo` | file | no | Imagen PNG, JPG o WebP de hasta 5 MB para el centro del código, como multipart/form-data |
| `logo_size` | string | no | Tamaño del logo. Por defecto medium (Ejemplo: `medium`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `url` | string | URL de descarga del fichero generado |
| `expires_at` | string | Cuándo se eliminará el fichero |
| `content` | string | El texto codificado en el QR |
| `format` | string | Formato generado |
| `size` | integer | Ancho pedido |
| `width` | integer | Ancho de la imagen en píxeles |
| `height` | integer | Alto de la imagen en píxeles |
| `warnings` | array | Avisos que pueden impedir escanearlo, como poco contraste. No bloquean la generación |

**Errores**

- `400`: Faltan datos, el contenido no cabe en un QR, o una opción o un color no son válidos
- `422`: El logo no se ha podido leer como imagen
- `503`: El servicio que procesa el logo no está disponible; reintenta en unos segundos

```bash
curl -X POST https://herramient.as/api/v1/tools/qr/google_review \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"link":"https://g.page/r/CabcdEFGH/review","format":"png","size":1024,"style":"rounded","eyes":"rounded","color":"#1e3a8a","eye_color":"#1e3a8a","background":"#ffffff","caption":true,"icon":"wifi","logo_size":"medium"}'
```

### QR de tarjeta de contacto

https://herramient.as/qr-tarjeta-contacto

#### `POST /api/v1/tools/qr/vcard`

Genera un código QR con una tarjeta de contacto (vCard 3.0)

Compone una vCard 3.0 con los campos escapados y el teléfono en formato internacional. Hacen falta el nombre y al menos un teléfono o un correo.

Consume 3 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `first_name` | string | sí | Nombre (Ejemplo: `Lucía`) |
| `last_name` | string | no | Apellidos (Ejemplo: `García Pérez`) |
| `organization` | string | no | Empresa (Ejemplo: `Panadería La Espiga`) |
| `title` | string | no | Cargo (Ejemplo: `Gerente`) |
| `phone` | string | no | Teléfono. Sin prefijo se entiende español (Ejemplo: `600 12 34 56`) |
| `email` | string | no | Correo electrónico (Ejemplo: `lucia@example.com`) |
| `website` | string | no | Web (Ejemplo: `https://example.com`) |
| `street` | string | no | Dirección (Ejemplo: `Calle Mayor 1`) |
| `postal_code` | string | no | Código postal (Ejemplo: `28013`) |
| `city` | string | no | Ciudad (Ejemplo: `Madrid`) |
| `format` | string | no | Formato de salida. Por defecto png (Ejemplo: `png`) |
| `size` | integer | no | Ancho en píxeles. Por defecto 512. Con firma, el alto es un 8 % mayor (Ejemplo: `1024`) |
| `style` | string | no | Forma de los módulos. Por defecto rounded (Ejemplo: `rounded`) |
| `eyes` | string | no | Forma de las tres esquinas. Por defecto rounded (Ejemplo: `rounded`) |
| `color` | string | no | Color del código en hexadecimal. Por defecto #111827 (Ejemplo: `#1e3a8a`) |
| `eye_color` | string | no | Color de las esquinas. Por defecto el de color (Ejemplo: `#1e3a8a`) |
| `background` | string | no | Color del fondo. Por defecto #ffffff (Ejemplo: `#ffffff`) |
| `caption` | boolean | no | Firma «creado con herramient.as» debajo del código. Por defecto true (Ejemplo: `true`) |
| `icon` | string | no | Icono para el centro del código, dibujado en vectorial. Se ignora si se manda logo (Ejemplo: `wifi`) |
| `logo` | file | no | Imagen PNG, JPG o WebP de hasta 5 MB para el centro del código, como multipart/form-data |
| `logo_size` | string | no | Tamaño del logo. Por defecto medium (Ejemplo: `medium`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `url` | string | URL de descarga del fichero generado |
| `expires_at` | string | Cuándo se eliminará el fichero |
| `content` | string | El texto codificado en el QR |
| `format` | string | Formato generado |
| `size` | integer | Ancho pedido |
| `width` | integer | Ancho de la imagen en píxeles |
| `height` | integer | Alto de la imagen en píxeles |
| `warnings` | array | Avisos que pueden impedir escanearlo, como poco contraste. No bloquean la generación |

**Errores**

- `400`: Faltan datos, el contenido no cabe en un QR, o una opción o un color no son válidos
- `422`: El logo no se ha podido leer como imagen
- `503`: El servicio que procesa el logo no está disponible; reintenta en unos segundos

```bash
curl -X POST https://herramient.as/api/v1/tools/qr/vcard \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"first_name":"Lucía","last_name":"García Pérez","organization":"Panadería La Espiga","title":"Gerente","phone":"600 12 34 56","email":"lucia@example.com","website":"https://example.com","street":"Calle Mayor 1","postal_code":"28013","city":"Madrid","format":"png","size":1024,"style":"rounded","eyes":"rounded","color":"#1e3a8a","eye_color":"#1e3a8a","background":"#ffffff","caption":true,"icon":"wifi","logo_size":"medium"}'
```

### QR de WhatsApp

https://herramient.as/qr-whatsapp

#### `POST /api/v1/tools/qr/whatsapp`

Genera un código QR que abre un chat de WhatsApp

Compone un enlace wa.me con el número en formato internacional y el mensaje inicial codificado. Un número de 9 cifras sin prefijo que empieza por 6, 7, 8 o 9 se entiende español (+34).

Consume 3 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `phone` | string | sí | Número de teléfono. Admite espacios, guiones y el prefijo con + o 00 (Ejemplo: `600 12 34 56`) |
| `message` | string | no | Mensaje que aparece ya escrito al abrir el chat (Ejemplo: `Hola, quiero reservar mesa`) |
| `format` | string | no | Formato de salida. Por defecto png (Ejemplo: `png`) |
| `size` | integer | no | Ancho en píxeles. Por defecto 512. Con firma, el alto es un 8 % mayor (Ejemplo: `1024`) |
| `style` | string | no | Forma de los módulos. Por defecto rounded (Ejemplo: `rounded`) |
| `eyes` | string | no | Forma de las tres esquinas. Por defecto rounded (Ejemplo: `rounded`) |
| `color` | string | no | Color del código en hexadecimal. Por defecto #111827 (Ejemplo: `#1e3a8a`) |
| `eye_color` | string | no | Color de las esquinas. Por defecto el de color (Ejemplo: `#1e3a8a`) |
| `background` | string | no | Color del fondo. Por defecto #ffffff (Ejemplo: `#ffffff`) |
| `caption` | boolean | no | Firma «creado con herramient.as» debajo del código. Por defecto true (Ejemplo: `true`) |
| `icon` | string | no | Icono para el centro del código, dibujado en vectorial. Se ignora si se manda logo (Ejemplo: `wifi`) |
| `logo` | file | no | Imagen PNG, JPG o WebP de hasta 5 MB para el centro del código, como multipart/form-data |
| `logo_size` | string | no | Tamaño del logo. Por defecto medium (Ejemplo: `medium`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `url` | string | URL de descarga del fichero generado |
| `expires_at` | string | Cuándo se eliminará el fichero |
| `content` | string | El texto codificado en el QR |
| `format` | string | Formato generado |
| `size` | integer | Ancho pedido |
| `width` | integer | Ancho de la imagen en píxeles |
| `height` | integer | Alto de la imagen en píxeles |
| `warnings` | array | Avisos que pueden impedir escanearlo, como poco contraste. No bloquean la generación |

**Errores**

- `400`: Faltan datos, el contenido no cabe en un QR, o una opción o un color no son válidos
- `422`: El logo no se ha podido leer como imagen
- `503`: El servicio que procesa el logo no está disponible; reintenta en unos segundos

```bash
curl -X POST https://herramient.as/api/v1/tools/qr/whatsapp \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"phone":"600 12 34 56","message":"Hola, quiero reservar mesa","format":"png","size":1024,"style":"rounded","eyes":"rounded","color":"#1e3a8a","eye_color":"#1e3a8a","background":"#ffffff","caption":true,"icon":"wifi","logo_size":"medium"}'
```

### QR de wifi

https://herramient.as/qr-wifi

#### `POST /api/v1/tools/qr/wifi`

Genera un código QR para conectarse a una red wifi

Compone el texto WIFI:T:...;S:...;P:...;; que leen las cámaras de iPhone y Android, con los caracteres especiales del nombre y la contraseña ya escapados, y lo dibuja con las mismas opciones de diseño que /qr/generate.

Consume 3 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `ssid` | string | sí | Nombre de la red, tal cual. Máximo 32 bytes (Ejemplo: `MiCasa_5G`) |
| `password` | string | no | Contraseña. Obligatoria salvo con security=nopass; con WPA, de 8 a 63 caracteres (Ejemplo: `una-clave-larga`) |
| `security` | string | no | WPA cubre WPA, WPA2 y WPA3. Por defecto WPA (Ejemplo: `WPA`) |
| `hidden` | boolean | no | Si la red no emite su nombre. Por defecto false (Ejemplo: `false`) |
| `format` | string | no | Formato de salida. Por defecto png (Ejemplo: `png`) |
| `size` | integer | no | Ancho en píxeles. Por defecto 512. Con firma, el alto es un 8 % mayor (Ejemplo: `1024`) |
| `style` | string | no | Forma de los módulos. Por defecto rounded (Ejemplo: `rounded`) |
| `eyes` | string | no | Forma de las tres esquinas. Por defecto rounded (Ejemplo: `rounded`) |
| `color` | string | no | Color del código en hexadecimal. Por defecto #111827 (Ejemplo: `#1e3a8a`) |
| `eye_color` | string | no | Color de las esquinas. Por defecto el de color (Ejemplo: `#1e3a8a`) |
| `background` | string | no | Color del fondo. Por defecto #ffffff (Ejemplo: `#ffffff`) |
| `caption` | boolean | no | Firma «creado con herramient.as» debajo del código. Por defecto true (Ejemplo: `true`) |
| `icon` | string | no | Icono para el centro del código, dibujado en vectorial. Se ignora si se manda logo (Ejemplo: `wifi`) |
| `logo` | file | no | Imagen PNG, JPG o WebP de hasta 5 MB para el centro del código, como multipart/form-data |
| `logo_size` | string | no | Tamaño del logo. Por defecto medium (Ejemplo: `medium`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `url` | string | URL de descarga del fichero generado |
| `expires_at` | string | Cuándo se eliminará el fichero |
| `content` | string | El texto codificado en el QR |
| `format` | string | Formato generado |
| `size` | integer | Ancho pedido |
| `width` | integer | Ancho de la imagen en píxeles |
| `height` | integer | Alto de la imagen en píxeles |
| `warnings` | array | Avisos que pueden impedir escanearlo, como poco contraste. No bloquean la generación |

**Errores**

- `400`: Faltan datos, el contenido no cabe en un QR, o una opción o un color no son válidos
- `422`: El logo no se ha podido leer como imagen
- `503`: El servicio que procesa el logo no está disponible; reintenta en unos segundos

```bash
curl -X POST https://herramient.as/api/v1/tools/qr/wifi \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"ssid":"MiCasa_5G","password":"una-clave-larga","security":"WPA","hidden":false,"format":"png","size":1024,"style":"rounded","eyes":"rounded","color":"#1e3a8a","eye_color":"#1e3a8a","background":"#ffffff","caption":true,"icon":"wifi","logo_size":"medium"}'
```

### Recortar Imagen para Redes

https://herramient.as/recortar-imagen-redes-sociales

#### `POST /api/v1/tools/images/crop`

Recorta una imagen a una medida exacta

width y height son obligatorios: la medida exacta es lo que distingue un recorte de un redimensionado. La imagen rellena esa caja y lo que sobra se recorta desde el centro. Para las medidas de cada red, la página de la herramienta las trae ya puestas. Responde con los bytes y no almacena nada.

Consume 3 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `file` | file | sí | La imagen, como multipart/form-data. Máximo 25 MB |
| `width` | integer | sí | Ancho exacto de salida, de 1 a 10000 (Ejemplo: `1080`) |
| `height` | integer | sí | Alto exacto de salida, de 1 a 10000 (Ejemplo: `1350`) |
| `format` | string | no | Formato de salida. Por defecto jpeg (Ejemplo: `jpeg`) |
| `quality` | integer | no | Calidad de 1 a 100. Por defecto 90 (Ejemplo: `90`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `(binario)` | string | Los bytes de la imagen. El Content-Type es image/<format> |

**Errores**

- `400`: Falta el fichero, falta width o height, o algún parámetro no es válido
- `422`: El fichero está dañado o no es una imagen que sepamos decodificar
- `503`: El servicio de procesamiento no está disponible. Reintenta según Retry-After

```bash
curl -X POST https://herramient.as/api/v1/tools/images/crop \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"width":1080,"height":1350,"format":"jpeg","quality":90}'
```

### Redimensionar Imagen

https://herramient.as/redimensionar-imagen

#### `POST /api/v1/tools/images/resize`

Cambia el tamaño de una imagen sin deformarla

Al menos una de width o height. La imagen se mete dentro de esas medidas manteniendo la proporción y **sin agrandarla nunca**: si pides 2000 px de una imagen de 800, recibes 800, y las cabeceras X-Image-Width y X-Image-Height dicen qué tamaño salió de verdad. Responde con los bytes de la imagen y no almacena nada.

Consume 3 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `file` | file | sí | La imagen, como multipart/form-data. Máximo 25 MB |
| `width` | integer | no | Ancho máximo en píxeles, de 1 a 10000 (Ejemplo: `1080`) |
| `height` | integer | no | Alto máximo en píxeles, de 1 a 10000 (Ejemplo: `1080`) |
| `format` | string | no | Formato de salida. Por defecto jpeg (Ejemplo: `png`) |
| `quality` | integer | no | Calidad de 1 a 100. Por defecto 90 (Ejemplo: `90`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `(binario)` | string | Los bytes de la imagen. El Content-Type es image/<format> |

**Errores**

- `400`: Falta el fichero, no se indicó ni width ni height, o algún parámetro no es válido
- `422`: El fichero está dañado o no es una imagen que sepamos decodificar
- `503`: El servicio de procesamiento no está disponible. Reintenta según Retry-After

```bash
curl -X POST https://herramient.as/api/v1/tools/images/resize \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"width":1080,"height":1080,"format":"png","quality":90}'
```

### Validador de IBAN

https://herramient.as/iban

#### `POST /api/v1/tools/iban/validate`

Valida un IBAN de cualquier país SEPA

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `iban` | string | sí | IBAN completo, con código de país (Ejemplo: `ES9121000418450200051332`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `valid` | boolean | Si el IBAN es válido |
| `country` | string | Código de país ISO |
| `formatted` | string | IBAN normalizado |
| `bank_code` | string | Código de entidad |
| `branch_code` | string | Código de oficina |
| `account_number` | string | Número de cuenta |
| `check_digits` | string | Dígitos de control del IBAN |
| `errors` | object | Detalle de los fallos cuando valid es false |
| `message` | string | Mensaje legible cuando valid es false |

**Errores**

- `400`: El IBAN está vacío

```bash
curl -X POST https://herramient.as/api/v1/tools/iban/validate \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"iban":"ES9121000418450200051332"}'
```

### Ver Metadatos de Foto

https://herramient.as/ver-metadatos-foto

#### `POST /api/v1/tools/images/metadata`

Lee los metadatos EXIF de una foto

Devuelve cámara, objetivo, fecha, parámetros de disparo y coordenadas GPS. **Admite JPEG y TIFF**, que es menos de lo que lee la página de la herramienta: ahí el reconocimiento ocurre en el navegador con una librería que además entiende PNG, HEIC, AVIF, IPTC y XMP. Si necesitas esos formatos, usa la página. La foto no se almacena.

Consume 2 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `file` | file | sí | La foto, como multipart/form-data. JPEG o TIFF. Máximo 25 MB |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `present` | boolean | Si la foto lleva metadatos. False es una respuesta válida, no un error |
| `camera` | string | Marca y modelo juntos |
| `lens` | string | Objetivo |
| `software` | string | Programa que la escribió o editó |
| `taken_at` | string | Fecha de la foto en ISO 8601 |
| `width` | integer | Ancho en píxeles según EXIF |
| `height` | integer | Alto en píxeles según EXIF |
| `orientation` | integer | Orientación EXIF, de 1 a 8 |
| `aperture` | number | Número f |
| `shutter_speed` | number | Tiempo de exposición en segundos |
| `iso` | integer | Sensibilidad ISO |
| `focal_length` | integer | Distancia focal en milímetros |
| `artist` | string | Autor declarado |
| `copyright` | string | Derechos declarados |
| `gps` | object | latitude, longitude y altitude. Null si la foto no lleva coordenadas. La longitud es negativa al oeste de Greenwich |

**Errores**

- `400`: Falta el fichero, no es un fichero o supera 25 MB
- `422`: El fichero está dañado o no es JPEG ni TIFF
- `503`: El servicio de procesamiento no está disponible. Reintenta según Retry-After

```bash
curl -X POST https://herramient.as/api/v1/tools/images/metadata \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Generales

#### `POST /api/v1/tools/identifier/detect`

Detecta y valida cualquier identificador español

Un solo endpoint para DNI, NIE, CIF, los NIF personales K, L y M y el número de afiliación a la Seguridad Social (NAF). Devuelve el tipo detectado y el desglose propio de ese tipo.

Consume 1 unidad de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `value` | string | sí | El identificador a comprobar (Ejemplo: `A28015865`) |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `valid` | boolean | Si el identificador es válido |
| `type` | string | Tipo detectado, null si no se reconoce |
| `checksum_valid` | boolean | Si el carácter de control cuadra |
| `normalized` | string | Entrada en mayúsculas y sin separadores |
| `details` | object | Desglose propio del tipo detectado |
| `message` | string | Mensaje legible del resultado |

**Errores**

- `400`: El identificador está vacío

```bash
curl -X POST https://herramient.as/api/v1/tools/identifier/detect \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"value":"A28015865"}'
```

#### `POST /api/v1/jobs`

Crea un trabajo asíncrono

Para las operaciones que no caben en una petición HTTP: el reconocimiento de texto de un escaneo y la conversión de vídeo tardan minutos, no milisegundos. Responde 202 con un identificador y el trabajo se ejecuta en segundo plano. Un solo recurso para todas: lo que separa un trabajo asíncrono de uno sincrónico es cuánto tarda, no qué hace, así que no hay una API por herramienta. Consulta el estado con GET /api/v1/jobs/{id} o pide un webhook. **Esta es la única familia que almacena el fichero.** El resto devuelve el resultado en la respuesta y no guarda nada; un trabajo que tarda minutos no tiene otra forma de existir. La entrada se borra en cuanto el trabajo acaba y el resultado a las 24 horas.

Consume 10 unidades de cuota.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `operation` | string | sí | Qué hacer con el fichero (Ejemplo: `video.convert`) |
| `file` | file | sí | El fichero, como multipart/form-data. Máximo 500 MB |
| `webhook_url` | string | no | URL pública a la que avisar al terminar. Tiene que ser http o https y no puede apuntar a una dirección interna o reservada (Ejemplo: `https://tu-app.example/hooks/htas`) |
| `language` | string | no | Solo para documents.ocr. Idioma del texto, por defecto spa (Ejemplo: `spa`) |
| `max_pages` | integer | no | Solo para documents.ocr. Páginas a reconocer, de 1 a 50 (Ejemplo: `10`) |
| `format` | string | no | Obligatorio para vídeo: mp4, mov, mkv, webm, gif, mp3, m4a, wav u ogg (Ejemplo: `mp4`) |
| `quality` | string | no | Para vídeo. Por defecto media (Ejemplo: `media`) |
| `height` | integer | no | Para vídeo. Alto máximo en píxeles, de 144 a 2160. No agranda un vídeo más pequeño (Ejemplo: `720`) |
| `mute` | boolean | no | Para vídeo. Quita la pista de audio |

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `id` | string | Identificador del trabajo, un UUID. Es lo que se usa en las otras dos llamadas |
| `operation` | string | La operación pedida |
| `status` | string | queued al crearse; después running, done, failed o cancelled |
| `progress` | integer | De 0 a 100 |
| `expires_at` | string | Cuándo se borra el resultado |

**Errores**

- `400`: Operación desconocida, falta el fichero, un parámetro no es válido, o la URL del webhook apunta a una dirección interna
- `503`: El servicio de procesamiento no está disponible. Reintenta según Retry-After

```bash
curl -X POST https://herramient.as/api/v1/jobs \
  -H "Authorization: Bearer htas_tu_clave_api" \
  -H "Content-Type: application/json" \
  -d '{"operation":"video.convert","webhook_url":"https://tu-app.example/hooks/htas","language":"spa","max_pages":10,"format":"mp4","quality":"media","height":720}'
```

#### `GET /api/v1/jobs/:token`

Consulta el estado de un trabajo

Esta es la fuente de verdad del estado, con webhook o sin él. Mientras `status` sea queued o running conviene esperar unos segundos entre consultas; un transcode de cinco minutos sondeado cada dos segundos son 150 peticiones para decir "sigue en curso", y para eso está el webhook.

No consume cuota.

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `id` | string | El identificador del trabajo |
| `status` | string | queued, running, done, failed o cancelled |
| `progress` | integer | De 0 a 100 |
| `error` | string | Código del motivo si falló, por ejemplo no_stream o corrupt_input |
| `error_message` | string | El motivo en texto, listo para mostrar |
| `result` | object | El resultado sin el fichero. Para OCR trae el texto; para vídeo, el formato y el tamaño |
| `result_url` | string | Dónde descargar el fichero, cuando hay uno y no ha caducado |
| `expires_at` | string | Cuándo se borra el resultado |

**Errores**

- `404`: No existe un trabajo con ese identificador en tu workspace

```bash
curl -X GET https://herramient.as/api/v1/jobs/:token \
  -H "Authorization: Bearer htas_tu_clave_api"
```

#### `GET /api/v1/jobs/:token/result`

Descarga el fichero de un trabajo terminado

Responde con los bytes del fichero. Los distintos "todavía no" tienen cada uno su código, para no obligar a adivinar: **202** si el trabajo sigue en curso (con Retry-After), **204** si la operación no produce fichero —el OCR devuelve texto, y está en el estado—, **410** si el resultado ya caducó y **422** si el trabajo falló.

No consume cuota.

**Respuesta**

| Nombre | Tipo | Descripción |
|---|---|---|
| `(binario)` | string | Los bytes del fichero, con su Content-Type y Content-Disposition |

**Errores**

- `202`: El trabajo todavía no ha terminado. Reintenta según Retry-After
- `204`: La operación no produce fichero; el resultado está en el estado del trabajo
- `404`: No existe un trabajo con ese identificador en tu workspace
- `410`: El resultado ha caducado y se ha borrado
- `422`: El trabajo falló

```bash
curl -X GET https://herramient.as/api/v1/jobs/:token/result \
  -H "Authorization: Bearer htas_tu_clave_api"
```
