Blog

Agent Skills en la API de Claude: cómo usar una skill lista para usar sin Claude Code

Markdown

Cómo conectar una skill pptx lista para usar a la API de Claude: container.skills, la herramienta de ejecución de código, la comprobación de la respuesta y la descarga de la presentación mediante la API Files.

Leer el artículo completo

Punto clave

Las Agent Skills listas para usar para documentos se pueden utilizar mediante la API directa de Claude: especifica la skill en container.skills y activa code_execution. Para tu primera solicitud, usa la combinación compatible de modelo y herramienta del ejemplo oficial. Descarga el archivo generado por separado usando su file_id; el ejemplo utiliza la misma clave de API para la solicitud y la descarga.

Encontrarás instrucciones adicionales en la documentación de Ailoot.

¿Qué necesitas para usar Agent Skills con la API de Claude?

Esta guía explica cómo llamar a la API directamente desde un programa, en lugar de instalar una skill en Claude Code. Necesitas una clave de API de Claude, acceso al modelo seleccionado y Python 3. El código siguiente utiliza la biblioteca estándar de Python y HTTP, por lo que no depende de la versión del SDK que tengas instalada. La clave se pasa mediante la variable de entorno ANTHROPIC_API_KEY; no la incluyas en el texto del programa ni en una conversación.

La API ofrece skills de Anthropic listas para usar: pptx para presentaciones, xlsx para hojas de cálculo, docx para documentos de Word y pdf para archivos PDF. Se conectan usando type: "anthropic". Esta guía utiliza una skill lista para usar, sin subir una personalizada.

La guía oficial de inicio rápido, consultada al preparar este material, utiliza la combinación claude-opus-5-5 y code_execution_20260521. Esa es la combinación que se usa a continuación. Este ejemplo se ha comprobado con la documentación; no es el informe de una llamada de pago a la API.

Qué encabezados beta se necesitan

El ejemplo sigue el contrato HTTP actual de la guía de inicio rápido: los encabezados son x-api-key, anthropic-version: 2023-06-01 y content-type: application/json; no envía anthropic-beta. No combines un modelo, un método de llamada y un formato de respuesta de distintas generaciones de la documentación.

La documentación de la API Files también indica que la API ya no está en beta. El encabezado antiguo files-api-2025-04-14 conserva los formatos de respuesta antiguos; al quitarlo, se activan los formatos actuales. Por eso no se puede combinar sin más un ejemplo antiguo que usa client.beta.files con uno nuevo.

Aquí no estamos migrando un servicio existente desde el antiguo contrato beta. Si ya tienes uno, comprueba primero sus encabezados y formatos de respuesta. La palabra beta en el nombre de un método del SDK no basta por sí sola: la documentación de la API Files explica por separado cómo se comportan las distintas versiones del SDK.

Crear una presentación y descargar el archivo

El ejemplo envía una tarea para crear cinco diapositivas sobre energía renovable, guarda la respuesta completa en skill-response.json y, después, extrae los identificadores de los archivos generados. No toma una ruta de guardado de la respuesta del modelo: los archivos se guardan en una nueva carpeta local llamada skill-output. Si esa carpeta ya existe, el programa se detiene para no sobrescribir el resultado anterior. La carpeta se crea antes de la llamada API, por lo que también permanece tras una llamada fallida. Inspecciona y conserva ese intento antes de repetir nada; no la borres solo para que una nueva ejecución continúe.

import json
import os
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen

API = "https://api.anthropic.com/v1"
KEY = os.environ["ANTHROPIC_API_KEY"]
HEADERS = {
    "x-api-key": KEY,
    "anthropic-version": "2023-06-01",
    "content-type": "application/json",
}

def request(path, payload=None, binary=False):
    data = None if payload is None else json.dumps(payload).encode("utf-8")
    req = Request(API + path, data=data, headers=HEADERS)
    try:
        with urlopen(req, timeout=600) as result:
            body = result.read()
    except HTTPError as error:
        try:
            details = json.loads(error.read().decode("utf-8"))
        except (ValueError, UnicodeError):
            details = {}
        info = details.get("error", {})
        request_id = error.headers.get("request-id") or details.get("request_id")
        diagnostic = {
            "http_status": error.code,
            "request_id": request_id,
            "error_type": info.get("type"),
            "message": str(info.get("message", "No JSON error message")).replace(KEY, "[REDACTED]")[:1000],
        }
        (out / "skill-error.json").write_text(
            json.dumps(diagnostic, ensure_ascii=False, indent=2), encoding="utf-8"
        )
        raise RuntimeError(
            f"Claude API HTTP {error.code}; request_id={request_id}; inspect skill-error.json"
        ) from None
    return body if binary else json.loads(body)

out = Path("skill-output")
out.mkdir(exist_ok=False)
response = request("/messages", {
    "model": "claude-opus-5-5",
    "max_tokens": 16000,
    "container": {
        "skills": [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
    },
    "messages": [{
        "role": "user",
        "content": "Crea una presentación sobre energías renovables con 5 diapositivas en mi idioma preferido.",
    }],
    "tools": [{"type": "code_execution_20260521", "name": "code_execution"}],
})
(out / "skill-response.json").write_text(
    json.dumps(response, ensure_ascii=False, indent=2), encoding="utf-8"
)
print("stop_reason:", response.get("stop_reason"))

file_ids = []
for block in response["content"]:
    if block["type"] == "bash_code_execution_tool_result":
        result = block["content"]
        if result["type"] == "bash_code_execution_result":
            for item in result["content"]:
                file_id = item.get("file_id")
                if file_id and file_id not in file_ids:
                    file_ids.append(file_id)
if not file_ids:
    raise RuntimeError("No generated file_id; inspect skill-response.json")

for index, file_id in enumerate(file_ids, start=1):
    from urllib.parse import quote
    file_path = "/files/" + quote(file_id, safe="")
    metadata = request(file_path)
    if metadata.get("downloadable") is not True:
        raise RuntimeError("This file cannot be downloaded through Files API")
    target = out / f"result-{index}.pptx"
    target.write_bytes(request(file_path + "/content", binary=True))
    print("Saved:", target)

Los nombres result-1.pptx, etc., se eligieron para esta tarea de presentación. La extensión de un archivo no demuestra qué contiene: abre el documento descargado y comprueba su formato, el número de diapositivas y el texto. No consideres que un HTTP 200 por sí solo, ni la presencia de un file_id, sean prueba de que el resultado se ha obtenido correctamente.

El programa muestra stop_reason, el motivo por el que terminó la respuesta, y guarda la respuesta completa para inspeccionarla. No continúa la conversación ni repite automáticamente la solicitud de pago. Tener un archivo descargado no significa que la tarea esté completa: inspecciona la respuesta guardada y el documento antes de decidir si hace falta otra solicitud.

Si falla un intento, inspecciona skill-error.json cuando exista. Conserva el estado HTTP, el ID de solicitud cuando se facilite y un mensaje del proveedor con longitud limitada y la clave API configurada eliminada. Revisa si contiene otros datos privados antes de compartirlo. Distingue errores de autenticación, permisos/modelo, facturación y formato con la referencia oficial; no todos requieren otra generación de pago.

Si skill-response.json ya contiene los file_id generados y solo falló una descarga, conserva esa respuesta y diagnostica metadatos o descarga con el mismo acceso autorizado al espacio de trabajo. No ejecutes de nuevo la generación solo para descargar un resultado existente. Si se desconoce el resultado de una solicitud, conserva su ID y aclara su estado antes de enviar otra generación. Solo cuando una nueva generación esté justificada, elige una carpeta sin usar, por ejemplo out = Path("skill-output-attempt-2"), y conserva exist_ok=False. Una carpeta nueva evita sobrescribir; no evita un cargo API duplicado.

Qué permisos de archivo se necesitan

La API Files asocia los archivos a un espacio de trabajo, no a un usuario individual de tu aplicación ni a una conversación. Según la documentación, cualquier clave de API con acceso a ese espacio de trabajo puede acceder a los archivos que se hayan subido allí. El ejemplo utiliza la misma clave para descargar y generar. Nunca aceptes un file_id de usuarios finales ni de otras fuentes no confiables. Guarda estos identificadores en el servidor y gestiona en tu propia aplicación la correspondencia entre los usuarios y sus archivos.

Comprueba por separado los metadatos del archivo: deben contener downloadable: true. La documentación permite descargar archivos generados por skills o por la herramienta de ejecución de código; los archivos que subes tienen downloadable: false y un intento de descarga devuelve 400. El acceso al espacio de trabajo y el permiso para descargar un archivo concreto son requisitos distintos.

Para una aplicación multiusuario, la documentación de la API Files recomienda un espacio de trabajo separado por cliente como límite de aislamiento de archivos. Esta recomendación se refiere específicamente a los archivos; no la extiendas a todas las entidades y permisos sin consultar la API pertinente.

Skills listas para usar y skills personalizadas

Las skills listas para usar tienen los identificadores pptx, xlsx, docx y pdf. En la referencia de la lista de skills, se describe el origen custom como privado para el espacio de trabajo, mientras que anthropic es compartido y de solo lectura.

version: "latest" significa en el primer ejemplo la última versión publicada de la skill. Para una integración reproducible, ten en cuenta que esta selección puede cambiar; para pasar a una versión fija, debes elegir una versión existente de la skill que utilizas, en lugar de inventar un identificador a partir de un ejemplo.

Limitaciones y el siguiente paso

El ejemplo solo cubre la creación de una presentación con una skill lista para usar y la descarga del archivo. Aquí no se han probado la carga de skills personalizadas, su eliminación ni el acceso a servicios de terceros. Esas tareas requieren una comprobación independiente de la API correspondiente.

Si tu tarea tiene que ver con archivos en la interfaz de Claude, hay una guía aparte sobre cómo subir un archivo a Claude. Para llamadas directas, consulta cómo calcular el uso de tokens de la API. Para trabajar con un agente de terminal, lee cómo elegir skills de Claude Code: se conectan de otra manera.

Para la primera comprobación basta con una skill pptx lista para usar: inspecciona la respuesta, descarga un archivo que esté permitido descargar y ábrelo. Añade skills personalizadas y separación de acceso entre clientes después de esa comprobación.

Cuando estés listo, descubre Ailoot.

Más detalles

Preguntas sobre Claude

¿Por qué un file_id no basta para dar por terminada una presentación?

El ID indica que se ha devuelto un archivo, no que sea una presentación utilizable. Comprueba en sus metadatos downloadable: true, descárgalo con acceso al espacio de trabajo y ábrelo para verificar formato, número de diapositivas y texto.

¿Puedo descargar cualquier archivo subido a la Files API de Claude?

No. La guía distingue archivos generados con downloadable: true y archivos subidos con downloadable: false. El acceso al espacio de trabajo y el permiso para descargar un archivo concreto son comprobaciones diferentes; no aceptes ID de archivos arbitrarios de usuarios finales.

Todos los artículos