# Lamina API > Run AI-powered apps (image generation, video creation, text processing) via API. Designed for AI agents. ## Base URL https://app.uselamina.ai ## Authentication All requests require an API key in the header: ``` x-api-key: lma_your_key_here ``` ## Endpoints ### List Apps GET /api/apps?search=&limit= Returns: { data: [{ appId, name, description, isPublic }] } ### Get App Details GET /api/apps/{appId} Returns: { data: { appId, name, description, parameters: [{ id, name, type, required, options, default }] } } Parameter types: - text: free-form string - options: one of the values from the options array - url: publicly accessible URL to image/video ### Run App POST /api/apps/{appId}/executions?webhook= Body: { "inputs": { "": "" } } Input keys are parameter **names** (from Get App), not IDs. For options parameters, use the **label** string. webhook query parameter is optional — if provided, results POST to that URL on completion. Returns: { data: { executionId, workflowId, status: "queued", webhookUrl, outputs: [...] } } ### Get Execution Status (polling) GET /api/executions/{executionId} Returns: { data: { executionId, workflowId, status, outputs: [{ id, label, type, value, status, error }], errorMessage, startedAt, completedAt, createdAt } } Status: queued → running → completed | failed Poll every 3-5 seconds until status is completed or failed. ### Get Webhook Signing Key GET /api/webhooks/signing-key Returns: { keys: [{ kty: "OKP", crv: "Ed25519", x: "", kid, use, alg }] } ## Webhooks Pass ?webhook=https://your-server.com/callback when running an app. On completion, we POST to your URL: - Same structure as polling response: { data: { executionId, status, outputs, ... } } - Headers: X-Lamina-Webhook-Signature (ED25519 hex), X-Lamina-Webhook-Timestamp (unix seconds), X-Lamina-Webhook-Request-Id (execution ID), X-Lamina-Webhook-User-Id (user ID that triggered it) - Signature message: "." - Retries: 3 attempts with backoff (5s, 30s, 2min) - Polling still works — you can use both webhooks and polling side by side ## Quick Integration (Python) ```python import requests API_KEY = "lma_your_key" BASE = "https://app.uselamina.ai/api" H = {"x-api-key": API_KEY, "Content-Type": "application/json"} # Find app apps = requests.get(f"{BASE}/apps?search=catalog", headers=H).json()["data"] app_id = apps[0]["appId"] # Get parameters app = requests.get(f"{BASE}/apps/{app_id}", headers=H).json()["data"] print(app["parameters"]) # shows what inputs to provide # Run with webhook r = requests.post(f"{BASE}/apps/{app_id}/executions?webhook=https://your-server.com/cb", headers=H, json={"inputs": {"Upload": "https://example.com/photo.jpg", "Style": "Ghibli"}}) exec_id = r.json()["data"]["executionId"] # Or poll for results import time while True: status = requests.get(f"{BASE}/executions/{exec_id}", headers=H).json()["data"] if status["status"] in ("completed", "failed"): for o in status["outputs"]: print(f"{o['label']}: {o['value']}") break time.sleep(5) ``` ## Quick Integration (Node.js) ```javascript const API_KEY = 'lma_your_key'; const BASE = 'https://app.uselamina.ai/api'; const headers = { 'x-api-key': API_KEY, 'Content-Type': 'application/json' }; // Run app with webhook const res = await fetch(`${BASE}/apps/${appId}/executions?webhook=${encodeURIComponent(webhookUrl)}`, { method: 'POST', headers, body: JSON.stringify({ inputs: { "Upload": "https://example.com/photo.jpg" } }), }); const { data } = await res.json(); console.log('Execution started:', data.executionId); // Or poll const poll = async (execId) => { while (true) { const r = await fetch(`${BASE}/executions/${execId}`, { headers }); const { data } = await r.json(); if (data.status === 'completed' || data.status === 'failed') return data; await new Promise(r => setTimeout(r, 5000)); } }; ``` ## Webhook Verification (Node.js) ```javascript const crypto = require('crypto'); function verifyLaminaWebhook(rawBody, signatureHex, timestamp, publicKeyDerBase64) { // Reject old timestamps (replay protection) if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; const publicKey = crypto.createPublicKey({ key: Buffer.from(publicKeyDerBase64, 'base64'), format: 'der', type: 'spki', }); const message = Buffer.from(`${timestamp}.${rawBody}`); return crypto.verify(null, message, publicKey, Buffer.from(signatureHex, 'hex')); } // In your webhook handler: app.post('/lamina-callback', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['x-lamina-webhook-signature']; const ts = req.headers['x-lamina-webhook-timestamp']; if (!verifyLaminaWebhook(req.body.toString(), sig, ts, PUBLIC_KEY_B64)) { return res.status(401).send('Invalid signature'); } const { data } = JSON.parse(req.body); console.log('Execution', data.executionId, data.status); data.outputs.forEach(o => console.log(o.label, o.value)); res.status(200).send('ok'); }); ``` ## Errors | Status | Meaning | |--------|---------| | 400 | Invalid inputs or webhook URL | | 401 | Missing or invalid API key | | 403 | No access to this app | | 404 | App or execution not found | | 500 | Server error |