API para agentes de GetFacade
Diseño de fachada sobre MCP y HTTP. Cada diseño se elabora para el país donde está el edificio: materiales aplicables allí, productos que los fabricantes venden realmente allí y la composición técnica detrás de la superficie. Un render lo muestra sobre la foto de la casa, el presupuesto lo valora línea a línea y el álbum PDF lo documenta para la empresa que lo ejecuta. Cada ruta de abajo es un endpoint existente de GetFacade, el mismo que llaman las apps de iOS, Android y web. Una clave de agente solo acota quién puede llamarlo, mide lo que gasta y se detiene en su tope.
- URL base
- https://api.getfacade.ai/api/v1
- Autenticación
- Bearer <agent key>
- Paquete
- @getfacade/mcp
- Entorno de ejecución
- Node.js 20+
- Transporte
- stdio (MCP), HTTPS (REST)
- Especificación
- OpenAPI 3.1, v1.0.0
Inicio rápido
Emitir una clave
app.getfacade.aiCuentaConfiguraciónAPICrear clave
El valor se muestra una sola vez y no se puede recuperar, solo reemplazar. El tope de gasto se fija al emitirla y se aplica en cada llamada de pago.
Emitir una clave de agenteRegistrar el servidor MCP
Una entrada en la configuración del cliente y, después, reiniciar el cliente. Claude Desktop la guarda en claude_desktop_config.json; cualquier otro cliente MCP admite los mismos tres campos.
{ "mcpServers": { "getfacade": { "command": "npx", "args": ["-y", "@getfacade/mcp"], "env": { "GETFACADE_API_KEY": "your-key" } } } }Variables de entorno Variable Obligatoria Valor por defecto GETFACADE_API_KEYSí —GETFACADE_API_BASE_URLNo https://api.getfacade.ai/api/v1O llamar directamente a la API HTTP
La misma clave sirve como token Bearer. Peticiones y respuestas son documentos JSON:API, donde id es un campo de primer nivel y nunca está dentro de attributes.
curl -X POST https://api.getfacade.ai/api/v1/projects \ -H "Authorization: Bearer $GETFACADE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"data":{"type":"project","attributes":{"name":"Maple Street 14"}}}'
Herramientas
Veintiuna herramientas. El servidor MCP no guarda estado ni reglas propias: cada herramienta es una o varias llamadas a los endpoints indicados al lado, y cada mensaje que el agente repite lo escribe la API.
create_buildingcreate_building(name, goals?, construction_region?) -> { building_id, name }Crea un edificio. El nombre es único dentro de la cuenta y tiene 50 caracteres como máximo; un duplicado se rechaza con 422.
Llama a
POST /projectsupload_photoupload_photo(building_id, file_path, wait_for_validation? = true) -> { view_id, validation: { status, reason? } }Registra una vista, sube los bytes a una URL prefirmada, los confirma y consulta hasta que la foto se acepta o se rechaza. Ancho, alto y md5 se calculan localmente; la relación de aspecto la deduce el servidor.
Llama a
POST /projects/{project}/angles → PUT (presigned) → POST /angles/{angle}/confirm → GET /angles/{angle}/validationstart_designasíncronastart_design(building_id, view_id, prompt?, style_ids?, colors?, brand_selections?, render_effort?, seed?) -> { design_id, job_id, status, seed }Diseña la fachada sobre una vista elegida y la muestra en la foto: materiales aplicables en el país del edificio, productos que allí se venden de verdad y la composición que hay detrás de la superficie. Crea un diseño, encola el trabajo y devuelve el id del trabajo. La semilla es opcional: si se omite, el servidor la genera y la devuelve.
Llama a
POST /projects/{project}/concepts → POST /concepts/{concept}/angles/{conceptAngle}/rendersrefine_designasíncronarefine_design(render_id | design_id + building_id, instruction, style_ids?, colors?, brand_selections?, render_effort?, seed?) -> { design_id, job_id, status, parent_render_id, seed }Revisa con palabras un diseño terminado. La instrucción se aplica al diseño terminado, así que se conserva todo lo que no menciona. Revisar una vista principal crea un diseño nuevo, de modo que el anterior nunca se sobrescribe.
Llama a
GET /renders/{render} → POST /concepts/{concept}/angles/{conceptAngle}/renders (mode: refine)get_jobget_job(job_id, kind: "render" | "album" | "estimate" = "render") -> { status, expected_seconds?, result_url?, error?, error_code? }Lee el estado de un render, un presupuesto o un álbum.
Llama a
GET /renders/{render} · GET /estimates/{estimate}list_jobslist_jobs(kind?, limit? = 20) -> [{ job_id, kind, status, building_id, created_at }]Trabajos recientes de la cuenta, los no terminados primero.
Llama a
GET /historylist_designslist_designs(building_id) -> [{ design_id, note, has_main_render, main_render_id, main_render_url, renders }]Diseños de un edificio con sus renders. De aquí salen los identificadores de render para un presupuesto o un álbum.
Llama a
GET /projects/{project}/conceptsorder_estimateasíncronaorder_estimate(design_id, render_ids, currency?, measurement_system?, special_requirements?) -> { job_id, status }Valora el diseño línea a línea, en materiales y mano de obra, al precio que tienen esos materiales en el país del edificio. La moneda y el sistema de medida se toman por defecto de ese país.
Llama a
POST /projects/{project}/concepts/{concept}/estimatesorder_albumasíncronaorder_album(design_id, render_ids, language?, include_blueprints?, include_estimate?, requirements?) -> { job_id, status }Documenta el diseño para la empresa que lo ejecuta: los materiales, la composición de la fachada, las notas de seguridad y las normas que las respaldan. Requiere un render principal terminado.
Llama a
POST /concepts/{concept}/album/generateupscale_renderasíncronaupscale_render(render_id) -> { job_id, status }Amplía un render terminado. Cuesta tokens y se ejecuta de forma asíncrona.
Llama a
POST /renders/{render}/upscaleget_estimateget_estimate(estimate_id) -> { status, currency, facade_area, materials_total, labor_total, grand_total, notes, lines: [{ line_id, section, name, quantity, unit, unit_price, line_total }] }El presupuesto en sí: totales, los supuestos que los sustentan y cada línea con cantidad, unidad y precio. get_job informa del estado de un presupuesto, nunca de su contenido.
Llama a
GET /estimates/{estimate}add_estimate_lineadd_estimate_line(estimate_id, section, name, quantity, unit_price, unit?, category?) -> { line_id }Añade una línea al presupuesto. Las unidades salen de su propio sistema de medida.
Llama a
POST /estimates/{estimate}/itemsupdate_estimate_lineupdate_estimate_line(estimate_id, line_id, name?, quantity?, unit_price?, unit?, category?, section?) -> { line_id }Cambia una línea del presupuesto. Solo se tocan los campos enviados; el servidor recalcula los totales.
Llama a
PATCH /estimates/{estimate}/items/{item}delete_estimate_linedelete_estimate_line(estimate_id, line_id) -> { deleted }Elimina una línea del presupuesto.
Llama a
DELETE /estimates/{estimate}/items/{item}delete_renderdelete_render(render_id) -> { deleted }Elimina un render. Al borrar el render principal, su diseño vuelve a borrador.
Llama a
DELETE /renders/{render}delete_designdelete_design(building_id, design_id) -> { deleted }Elimina un diseño con los renders que contiene.
Llama a
DELETE /projects/{project}/concepts/{concept}delete_buildingdelete_building(building_id) -> { deleted }Elimina un edificio con todo lo que contiene. Los tokens ya gastados no se devuelven.
Llama a
DELETE /projects/{project}list_token_packageslist_token_packages() -> [{ package, tokens, price, currency }]Los paquetes que esta cuenta puede comprar, con su precio y su número de tokens.
Llama a
GET /tokens/packagesbuy_tokensasíncronabuy_tokens(package) -> { status, transaction_id, tokens, checkout_url?, detail }Compra un paquete para la cartera de esta clave. Requiere una clave emitida con la compra habilitada y nunca supera lo que la clave aún puede gastar.
Llama a
POST /tokens/purchaseget_balanceget_balance() -> { balance, scope: "api", spend_cap, spent, remaining, is_admissible }Monedero del agente, tope de esta clave y si se admitirá la próxima llamada de pago.
Llama a
GET /tokens/balancereport_problemreport_problem(message, category?, context?: { tool, endpoint, status_code, job_id, expected, actual }) -> { reference, message }Informa de un defecto de esta API: un campo descrito aquí que nunca llega, un rechazo cuya redacción no deja salida, un resultado que no corresponde a lo pedido. Gratuito y aceptado con el saldo a cero; devuelve una referencia, no una respuesta.
Llama a
POST /feedback
Autenticación y claves
- La clave viaja como token Bearer en la cabecera
Authorization. El servidor MCP la lee deGETFACADE_API_KEYy no envía nada más. - El valor se muestra una vez, al emitirlo, y solo se guarda su hash. Rotar significa emitir una clave nueva y revocar la anterior.
- Cada clave lleva un tope de gasto que se aplica en el servidor antes de que la llamada llegue a un controlador. Alcanzar el tope detiene esa clave, no la cuenta.
- Las claves no gestionan claves: eso es una acción humana, y esos endpoints responden 403 a una clave de agente.
- La revocación surte efecto de inmediato. Las llamadas con una clave revocada responden 401.
Trabajo asíncrono y consulta de estado
start_design,order_estimateyorder_albumdevuelven un identificador de trabajo y terminan. Un render tarda minutos: consulteGET /renders/{render}oGET /estimates/{estimate}hasta que el estado sea terminal.- El fin de la validación de la foto lo anuncia un websocket del que el agente no dispone. Consulte
GET /angles/{angle}/validationy leavalidation.is_in_progress; no deduzca usted mismo la terminalidad de la cadena de estado. - Un render terminado y un álbum terminado viven en URL públicas permanentes: sin firma y sin caducidad. El enlace se le puede dar directamente a una persona, como respuesta a «enséñame el resultado». Al no estar firmado no pide permiso a nadie: sigue funcionando para cualquiera que lo reciba y no se puede revocar.
GET /renders/{render}/downloades otra cosa: una URL firmada que caduca en minutos y lleva un nombre de archivo. Sirve para guardar el archivo, no para compartirlo.
Límites de frecuencia por clave
Una clave de agente tiene sus propios cupos, separados de las sesiones humanas de la misma cuenta, para que un agente en bucle no consuma el cupo de quien está frente a la pantalla. El rechazo es barato: se decide en el middleware, antes de cualquier trabajo en la base de datos.
| Ámbito | Por minuto | Por hora |
|---|---|---|
| Lecturas y escrituras ordinarias | 120 | 2000 |
| Consulta de estados y de validación | 120 | 2000 |
| Encargos de render, presupuesto y álbum | 10 | 200 |
Idempotencia
Una llamada de pago crea un trabajo, y el cargo sigue al trabajo. Poner nombre a la llamada es lo que permite que un reenvío devuelva el mismo trabajo en vez de crear un segundo.
Idempotency-Keyes obligatoria en toda llamada de pago hecha con una clave API: iniciar o retocar un diseño, ampliar un render, pedir un presupuesto o un álbum, regenerar un presupuesto. Sin ella la llamada responde 422IDEMPOTENCY_KEY_REQUIREDy no se encola nada.- Cualquier valor de 8 a 191 caracteres, uno por pedido; lo habitual es un UUID. Un pedido nuevo lleva un valor nuevo: dos llamadas idénticas bajo dos valores son dos diseños.
- Repetir una llamada con el mismo valor y el mismo cuerpo devuelve el estado y el cuerpo originales, con
Idempotent-Replay: trueen la respuesta. No se encola nada ni se cobra dos veces. - El mismo valor con un cuerpo distinto responde 422
IDEMPOTENCY_KEY_REUSED. Un reenvío que llega mientras la primera llamada sigue en curso responde 409IDEMPOTENCY_IN_PROGRESS: espera y envía la misma llamada otra vez. - Cualquier 4xx libera el valor, así que se puede volver a enviar una vez corregida la causa. Los valores se recuerdan 24 horas, por cuenta.
@getfacade/mcpgenera el valor y reintenta con él por su cuenta, así que en la llamada a la herramienta no hay que pasar nada.
Errores
Los fallos llegan como documentos de error JSON:API. Las respuestas de validación de Laravel no tienen forma JSON:API y llevan su texto en message.
| Estado | Código | Significado | Reintentable |
|---|---|---|---|
401 | — | La clave falta, está revocada o ha caducado. | No |
402 | AGENT_CREDITS_EXHAUSTED | La cuenta no tiene créditos de ámbito api. | No |
402 | AGENT_KEY_CAP_REACHED | Esta clave ha agotado su tope. Emita otra clave o suba el tope. | No |
403 | — | Este endpoint no está disponible para claves de API. La API para agentes abarca edificios, fotos, diseños, renders, presupuestos, álbumes y el monedero de API. Los ajustes de cuenta, inicio de sesión y pago los cambia una persona que ha iniciado sesión en la aplicación. | No |
403 | AGENT_PURCHASE_NOT_ALLOWED | Esta clave se emitió sin permiso para comprar tokens. | No |
403 | AGENT_PURCHASE_EXCEEDS_CAP | La compra dejaría la clave por encima de su límite de gasto. | No |
409 | IDEMPOTENCY_IN_PROGRESS | La primera llamada con este Idempotency-Key aún no ha respondido. Espera y envía la misma llamada de nuevo. | Sí |
422 | — | La petición se entendió y se rechazó: nombre de edificio duplicado, foto rechazada, álbum encargado antes de terminar el render principal. | No |
422 | IDEMPOTENCY_KEY_REQUIRED | Una llamada de pago con clave API y sin cabecera Idempotency-Key. No se encoló nada; vuelve a enviarla con la cabecera. | No |
422 | IDEMPOTENCY_KEY_REUSED | Este Idempotency-Key se usó para otra solicitud. Usa un valor nuevo para un pedido nuevo. | No |
429 | — | El cupo propio de esta clave está agotado. Espere, no reintente en bucle cerrado. | Sí |
El texto legible lo escribe la API, en el idioma de quien llama. Muestre errors[].detail tal cual en lugar de componer su propio mensaje.
Facturación y admisión
- Las llamadas de pago consumen créditos del ámbito
api, y la admisión solo mira ese saldo: cada clave paga en créditos. - Un Pro Plan activo repone la cartera
apihasta 1000 créditos una vez por periodo de facturación. Más allá, los créditos se compran. - Una clave recarga su cartera solo si se emitió con la compra habilitada, y solo hasta lo que aún puede gastar, así que una compra nunca sube el límite de gasto.
- El ámbito
apies un monedero aparte. Los créditos de la aplicación, incluida la capa gratuita, nunca los gasta una clave. - El gasto se mide por clave, así que el consumo de cada asistente se ve por separado.
- La comprobación previa es
GET /tokens/balance, campodata.attributes.agent.is_admissible. El bloque solo aparece en claves de agente y el indicador refleja exactamente el middleware de admisión. Léalo en lugar de comparar el saldo con el tope. - La admisión se decide antes de encolar trabajo, así que una llamada rechazada no gasta nada.
Tokens de color y de marca
start_design admite dos listas independientes de diez entradas como máximo. El orden lleva el papel 60/30/10: la primera entrada es el color dominante de los muros.
colors
| Token | Significado |
|---|---|
palette:1 | Una combinación de GetFacade, por identificador. |
#8A8F7D | Un color libre, seis dígitos hexadecimales. |
paint:412 | Una muestra de fabricante, en la forma de dos segmentos que se mantiene por compatibilidad. |
brand_selections
| Token | Significado |
|---|---|
siding:brand:12 | Cualquier producto de ese fabricante en esa categoría. |
siding:line:40@double-4-dutchlap | Una línea, sobre una geometría. |
siding:product:88@double-4-dutchlap | Un producto, totalmente especificado. |
paint:brand:3 | Cualquier color de esa marca de pintura. |
paint:product:412 | Una muestra de pintura. |
La gramática es category:level:id[@value][.value]. La parte tras @ lleva slugs de valores de geometría, únicos dentro de su categoría, de modo que el eje al que pertenecen se busca en lugar de escribirse en el token. Un token desconocido se rechaza con 422 y nunca se ignora en silencio.
Sesión de principio a fin
Un edificio, una foto, un diseño y luego los dos documentos. La instrucción que lo produce:
Crea un edificio llamado Maple Street 14, sube ./front.jpg como su vista e inicia un diseño con muros gris cálido y molduras blancas. Encarga el presupuesto y el álbum del resultado.
create_building(name: "Maple Street 14")
-> { building_id: "0f8c…" }
upload_photo(building_id: "0f8c…", file_path: "./front.jpg")
-> { view_id: "41ab…", validation: { status: "approved" } }
start_design(building_id: "0f8c…", view_id: "41ab…",
colors: ["#8A8F7D", "#F2F0EB"],
brand_selections: ["siding:line:40@double-4-dutchlap"])
-> { design_id: "7d21…", job_id: "b933…", status: "queued" }
get_job(job_id: "b933…")
-> { status: "completed", result_url: "https://…" }
refine_design(render_id: "b933…",
instruction: "put a canopy over the front door")
-> { design_id: "9e44…", job_id: "c07f…", status: "queued" }
order_estimate(design_id: "9e44…", render_ids: ["c07f…"])
order_album(design_id: "9e44…", render_ids: ["c07f…"], include_estimate: true)