API do Website
Alimente o seu próprio website com o catálogo de equipamento, disponibilidade, estimativas de preço, pedidos de aluguer e reservas diretas em tempo real. O catálogo e a disponibilidade são só de leitura; também pode submeter pedidos de aluguer e criar reservas — tudo restrito ao seu workspace por uma chave de API.
Início rápido
Tudo o que precisa para começar a integrar o seu catálogo no seu site.
- No RunAV, abra Definições → API para Programadores e defina o slug da sua loja.
- Crie uma chave de API e copie-a (é mostrada apenas uma vez).
- Chame a API com a sua chave no cabeçalho X-Api-Key, usando o seu slug no caminho.
URL base (substitua {slug} pelo slug da sua loja):
https://app.runav.io/api/public/v1/{slug}curl -H "X-Api-Key: YOUR_KEY" https://app.runav.io/api/public/v1/{slug}/catalogAutenticação
Cada pedido (exceto as imagens do catálogo) deve incluir a sua chave secreta no cabeçalho X-Api-Key. As chaves são criadas e revogadas em Definições → API para Programadores.
X-Api-Key: rfk_live_xxxxxxxxxxxxxxxxxxxxxxxx
As chaves estão associadas ao seu workspace e plano. Se o seu plano deixar de incluir acesso à API, ou se uma chave for revogada, os pedidos devolvem 401/403. Nunca exponha uma chave em código de cliente que não controla — pode submeter pedidos de aluguer em seu nome.
Âmbitos
Cada chave de API tem âmbitos que limitam o que pode fazer. Conceda apenas o que a integração precisa.
catalog:read read catalog, availability, quote requests:write submit rental requests bookings:read read a booking's status bookings:write create bookings
Endpoints
O array items
Os endpoints /quote, /rental-requests e /bookings recebem items como um array de { "model": string, "quantity": number }. O model tem de ser um valor de GET /catalog — o seu model_key (recomendado) ou o seu name exato. Outros nomes de chave (name, product, sku, qty) ou strings simples não são reconhecidos. A quantity é opcional e limitada a 1–999 (predefinição 1); o array está limitado a 100 artigos. Um kit reserva-se da mesma forma — passe o seu model_key como model; um kit é um conjunto único, por isso a sua quantity é sempre tratada como 1. Se um nome for partilhado por um modelo de equipamento e um kit, o modelo de equipamento prevalece; adicione "type": "kit" (ou "equipment") a um artigo para forçar qual deles é resolvido.
Atenção: em /rental-requests e /bookings, qualquer artigo com um model em falta, vazio ou com o nome de chave errado é descartado silenciosamente — o pedido devolve success na mesma, mas guarda uma lista de artigos vazia. Construa items a partir dos valores model_key/name de GET /catalog e confirme que o pedido guardado mostra as suas linhas. Em /quote, os modelos não reconhecidos vêm como linhas unavailable, por isso a discrepância é visível.
/catalogDevolve o seu equipamento publicado, uma entrada por modelo, com um URL de imagem e uma tarifa diária ou um indicador de preço sob consulta. O campo category é um array ordenado da raiz até à folha (por exemplo ["Video", "Camera"]), ou null. O stock aparece apenas como um booleano in_stock, nunca como uma contagem de unidades. O campo availability_display reflete o GET /availability: quando a exibição de disponibilidade está definida como oculta, é "hidden" e o indicador in_stock é omitido de todas as entradas. Os serviços publicados são anexados quando ativados. Os kits publicados (conjuntos) também são anexados, cada um com "type": "kit" e sem indicador in_stock (verifique a disponibilidade de um kit via GET /availability). Um kit inclui também um array items com o seu conteúdo agrupado por modelo — [{ name, brand, model, quantity, image_url }] — para poder mostrar o que está dentro do conjunto (seguro para o público: apenas nomes, quantidades e imagens dos itens, nunca números de série ou preços por unidade). O image_url de um kit é a sua imagem carregada, se tiver uma; caso contrário, um mosaico 2x2 que a API compõe a partir das imagens dos primeiros itens (imagem própria, senão mosaico, senão null) — utilizável como um único img ou og:image. Um kit pode ser definido para excluir as suas unidades, caso em que essas unidades deixam de aparecer como entradas individuais — mas quaisquer unidades extra do mesmo modelo que não estejam no kit continuam a aparecer.
O campo description é escrito em Markdown (negrito, itálico, títulos, listas numeradas e com marcadores). É devolvido tal como foi escrito, por isso apresente-o através do seu próprio renderizador de Markdown e sanitize o resultado antes de o mostrar.
curl -H "X-Api-Key: YOUR_KEY" \
https://app.runav.io/api/public/v1/{slug}/catalogResposta
{
"storefront_enabled": true,
"availability_display": "status",
"models": [
{
"model_key": "shure-sm58--sm58-lc",
"name": "Shure SM58",
"brand": "Shure",
"model": "SM58-LC",
"category": ["Audio", "Microphones"],
"description": "**Dynamic** vocal microphone — *industry standard* for live vocals.",
"tags": ["vocal", "wired"],
"featured": false,
"in_stock": true,
"daily_rate": 12.5,
"price_display": "exact",
"image_url": "https://app.runav.io/api/public/v1/{slug}/catalog/image?name=Shure%20SM58&model=SM58-LC"
},
{
"model_key": "meyer-panther--panther-m",
"name": "Meyer Panther",
"brand": "Meyer Sound",
"model": "PANTHER-M",
"category": ["Audio", "Line Arrays"],
"description": "Large-format line array",
"tags": ["touring"],
"featured": true,
"in_stock": true,
"price_on_request": true,
"image_url": null
},
{
"model_key": "video-summary",
"type": "service",
"name": "Video Summary",
"category": ["Production", "Video"],
"description": "Same-day highlights edit delivered within 48h.",
"featured": true,
"image_url": "https://app.runav.io/api/public/v1/{slug}/catalog/service-image?service=8f1e...c2",
"price": 2300,
"price_unit": "fixed"
},
{
"model_key": "vocal-wireless-pack",
"type": "kit",
"name": "Vocal Wireless Pack",
"category": ["Audio", "Packages"],
"description": "Four-channel handheld wireless kit.",
"featured": false,
"daily_rate": 180,
"price_display": "exact",
"image_url": "https://app.runav.io/api/public/v1/{slug}/catalog/kit-image?kit=8f1e...c2",
"items": [
{ "name": "Shure SM58", "brand": "Shure", "model": "SM58-LC", "quantity": 4, "image_url": "https://app.runav.io/api/public/v1/{slug}/catalog/image?name=Shure%20SM58&model=SM58-LC" },
{ "name": "Shure BLX88", "brand": "Shure", "model": "BLX88", "quantity": 1, "image_url": null }
]
}
]
}/catalog/image?name=NAME&model=MODELSem chaveServe a imagem de um modelo, estritamente para o (name, model) exato — não há fallback ao nível do nome, por isso dois modelos com o mesmo nome nunca partilham imagem. Pública (sem chave) para a poder incorporar diretamente numa tag <img>; só são servidos modelos publicados. Prefira o image_url devolvido por GET /catalog (já é o URL correto por modelo) em vez de construir este URL manualmente; é null quando esse modelo específico não tem imagem.
<img src="https://app.runav.io/api/public/v1/{slug}/catalog/image?name=Shure%20SM58&model=SM58-LC" />/availability?start=YYYY-MM-DD&end=YYYY-MM-DDDevolve um estado de disponibilidade por modelo (disponível, limitado ou indisponível) num intervalo de datas, usando a mesma lógica de reservas do agendador da aplicação. As contagens de unidades nunca são expostas. Opcionalmente, filtre para um modelo com o parâmetro de consulta model.
curl -H "X-Api-Key: YOUR_KEY" \
"https://app.runav.io/api/public/v1/{slug}/availability?start=2026-07-01&end=2026-07-05"Resposta
{
"start": "2026-07-01",
"end": "2026-07-05",
"availability_display": "status",
"models": [
{ "model_key": "shure-sm58--sm58-lc", "name": "Shure SM58", "model": "SM58-LC", "status": "available" },
{ "model_key": "meyer-panther--panther-m", "name": "Meyer Panther", "model": "PANTHER-M", "status": "limited" }
]
}/request-formDevolve o contrato do formulário de pedido: que campos cada tipo de requerente (particular ou empresa) tem de enviar, a configuração de mensagem/termos, e a oferta de entrega com a sua restrição geográfica (whitelist ou blacklist de países e cidades). Leia-o primeiro para que o seu formulário só ofereça locais servidos e valide antes de submeter.
curl -H "X-Api-Key: YOUR_KEY" \
https://app.runav.io/api/public/v1/{slug}/request-formResposta
{
"requester_types": ["individual", "company"],
"required": {
"individual": ["contact_name", "contact_email", "contact_phone", "vat_number"],
"company": ["contact_name", "contact_email", "contact_phone", "company", "vat_number", "company_address"]
},
"message": "optional",
"require_terms": false,
"terms_text": {},
"intro": {},
"delivery": {
"enabled": true,
"restriction": { "mode": "whitelist", "zones": [{ "country": "PT" }, { "country": "ES", "city": "Madrid" }] }
},
"rules": { "requireDates": true, "leadTimeDays": 0, "maxAdvanceDays": null, "maxQtyPerModel": null, "maxItemsPerRequest": null, "blackoutDates": [] }
}/quoteDevolve uma estimativa de preço não vinculativa para uma seleção de modelos num intervalo de datas, mais quaisquer violações de regras de aluguer. Os modelos com preço oculto são marcados como price_on_request.
curl -X POST -H "X-Api-Key: YOUR_KEY" -H "Content-Type: application/json" \
-d '{ "start": "2026-07-01", "end": "2026-07-05", "items": [{ "model": "shure-sm58--sm58-lc", "quantity": 2 }] }' \
https://app.runav.io/api/public/v1/{slug}/quoteResposta
{
"estimate": true,
"price_on_request": false,
"days": 4,
"currency": { "symbol": "€", "code": "EUR" },
"lines": [{ "model": "Shure SM58", "quantity": 2, "daily_rate": 12.5 }],
"subtotal": 100.0,
"vat_rate": 23,
"vat": 23.0,
"total": 123.0,
"rule_violations": []
}/rental-requestsSubmete um pedido de aluguer. Aparece na sua caixa de entrada de Pedidos de Aluguer para revisão e confirmação. O requerente submete como particular (nome, email, telefone e NIF obrigatórios) ou empresa (adicionalmente o nome, NIF e morada da empresa; cada morada é { country, city, address, postal_code } com país ISO alpha-2). Um bloco delivery opcional pede entrega nessa morada (validada contra a restrição de entrega — ver GET /request-form); omita-o para levantamento no armazém. Validado contra as regras da sua storefront e com limite de taxa.
curl -X POST -H "X-Api-Key: YOUR_KEY" -H "Content-Type: application/json" \
-d '{ "requester_type": "company", "contact_name": "Jane Doe", "contact_email": "jane@example.com", "contact_phone": "+351 900 000 000", "vat_number": "PT123456789", "company": "Doe Productions", "company_address": { "country": "PT", "city": "Lisboa", "address": "Rua Exemplo 1", "postal_code": "1000-001" }, "delivery": { "country": "PT", "city": "Lisboa", "address": "Av. da Entrega 42", "postal_code": "1000-002" }, "message": "Need these for a weekend shoot.", "start": "2026-07-01", "end": "2026-07-05", "items": [{ "model": "shure-sm58--sm58-lc", "quantity": 2 }] }' \
https://app.runav.io/api/public/v1/{slug}/rental-requestsResposta
{ "success": true, "id": "…" }Em caso de recusa (400)
A validação usa as regras da sua storefront. Um pedido recusado devolve 400 com um array rule_violations — cada entrada tem um code (e uma mensagem legível). Códigos possíveis: requester_type_invalid, phone_required, vat_required, company_required, company_address_required, delivery_not_offered, delivery_address_required, delivery_not_served, message_required, terms_required, dates_required, below_lead_time, above_max_advance, blackout, too_many_items, qty_too_high, exceeds_availability, unavailable_item. Os códigos de disponibilidade (exceeds_availability, unavailable_item) só se aplicam quando start e end são fornecidos. Uma storefront fechada devolve 403.
{
"error": "Your request could not be submitted.",
"rule_violations": [
{ "code": "vat_required", "field": "vat_number", "message": "A VAT number is required." },
{ "code": "delivery_not_served", "field": "delivery", "message": "Sorry — we don't deliver to Faro, Portugal." },
{ "code": "exceeds_availability", "model": "shure-sm58--sm58-lc", "requested": 5, "available": 2 }
]
}/bookingsCriar uma reserva diretamente: revalida a disponibilidade, calcula o preço e cria um trabalho. O bloco contact segue o mesmo contrato de identidade do requerente que /rental-requests (type, phone e vat_number obrigatórios; empresas enviam também company + company_address), e um bloco delivery opcional de topo é validado da mesma forma — uma reserva com entrega coloca a morada na localização do trabalho. Devolve 400 com rule_violations para problemas de identidade/entrega, e 409 com um array shortfalls se os artigos deixarem de estar disponíveis. Envie um cabeçalho Idempotency-Key para que as repetições não dupliquem. Requer o âmbito bookings:write.
curl -X POST -H "X-Api-Key: YOUR_KEY" -H "Idempotency-Key: order-123" -H "Content-Type: application/json" \
-d '{ "start": "2026-07-01", "end": "2026-07-05", "items": [{ "model": "shure-sm58--sm58-lc", "quantity": 2 }], "contact": { "type": "individual", "name": "Jane Doe", "email": "jane@example.com", "phone": "+351 900 000 000", "vat_number": "PT123456789" }, "delivery": null }' \
https://app.runav.io/api/public/v1/{slug}/bookingsResposta
{
"success": true,
"id": "…",
"client_id": "…",
"status": "pending",
"total": 123.0,
"currency": { "symbol": "€", "code": "EUR" },
"days": 4
}/bookings/:idLer o estado atual de uma reserva. Requer o âmbito bookings:read.
curl -H "X-Api-Key: YOUR_KEY" https://app.runav.io/api/public/v1/{slug}/bookings/JOB_IDResposta
{ "id": "…", "status": "pending", "start_date": "2026-07-01", "end_date": "2026-07-05", "total": 100.0 }Webhooks
Subscreva um URL (em Definições → API para Programadores) a eventos. Enviamos um payload JSON assinado (POST) quando cada evento ocorre, com repetições automáticas.
Eventos
rental_request.created booking.created booking.confirmed quote.accepted
Cada pedido inclui os cabeçalhos Webhook-Id, Webhook-Timestamp e Webhook-Signature. A assinatura é HMAC-SHA256 em base64 sobre `{id}.{timestamp}.{rawBody}` usando o segredo de assinatura do seu endpoint.
POST https://your-app.example.com/webhooks/runav
Webhook-Id: <delivery id>
Webhook-Timestamp: <unix seconds>
Webhook-Signature: v1,<base64 HMAC-SHA256>
Content-Type: application/json
{ "id": "…", "type": "booking.created", "created": 1730000000, "data": { … } }Verificar uma assinatura
// Node — verify the signature
import { createHmac, timingSafeEqual } from 'crypto';
const secret = process.env.RUNAV_WEBHOOK_SECRET.replace(/^whsec_/, '');
const signed = `${webhookId}.${webhookTimestamp}.${rawBody}`;
const expected = createHmac('sha256', Buffer.from(secret, 'base64')).update(signed).digest('base64');
const provided = signatureHeader.split(',')[1]; // after "v1,"
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(provided));Respostas não-2xx são repetidas com recuo exponencial (até 6 tentativas). Responda 2xx rapidamente e faça o trabalho pesado de forma assíncrona.
Limites e erros
Os erros devolvem um corpo JSON com um único campo de mensagem:
{ "error": "A human-readable message" }Os pedidos de aluguer estão limitados a cerca de 10 submissões por 10 minutos por chave e IP.
CORS
A API pública pode ser chamada a partir de qualquer origem no browser, por isso pode chamá-la diretamente do frontend do seu website. O cabeçalho X-Api-Key é a única credencial — não são usados cookies.