O Portugal Empresarial tem uma API REST (versão 1) para ligar o seu CRM, ERP ou folha de cálculo aos dados do site: diretório de empresas, oportunidades abertas, os pedidos de cotação da sua empresa, as propostas e as ligações. A API responde em JSON. É de leitura, com uma exceção: pode enviar e alterar o catálogo de produtos e serviços da sua empresa (em todos os planos; no Gratuito até 25 produtos) (ver Escrever no catálogo de produtos).
A documentação técnica pública está em Documentação da API (ligação «API para integrações» no rodapé).
Criar um token

O acesso à API é feito com tokens pessoais. Cada token pertence a uma pessoa e atua em nome da empresa a que essa pessoa pertence.
- Abra Definições da conta › secção Tokens da API (/definicoes).
- Em Nome do token, escreva um nome que identifique a integração (entre 2 e 60 caracteres; ex.: «HubSpot», «Primavera»).
- Carregue em Criar token.
- Aparece o cartão Copie o novo token agora. Carregue em Copiar e guarde o token num local seguro.
Regras:
- Precisa de sessão iniciada com o email confirmado.
- Cada pessoa pode ter até 10 tokens ativos («Atingiu o limite de 10 tokens. Revogue um token antes de criar outro.»).
- Todos os tokens têm a permissão read (leitura). Para enviar o catálogo de produtos, marque Permitir escrita ao criar o token: o token recebe também a permissão write. Crie tokens de escrita só para as integrações que precisam deles.
Gerir e revogar tokens
A lista mostra cada token com Criado em, Último uso (ou Nunca usado) e Permissões. Para revogar, carregue em Revogar e confirme «Revogar este token? As integrações que o usam deixam de funcionar.» O efeito é imediato.
Autenticação
Envie o token em todos os pedidos, no cabeçalho Authorization, junto com Accept: application/json:
Authorization: Bearer 1|o-seu-token
Accept: application/json
URL base: https://<endereço do site>/api/v1
Endpoints

Os endpoints de leitura usam o método GET; os de escrita do catálogo estão em Escrever no catálogo de produtos.
| Endpoint | O que devolve | Parâmetros |
|---|---|---|
/me |
O utilizador dono do token, a empresa em nome da qual atua e o papel nessa empresa (owner, admin ou member). |
— |
/companies |
Diretório de empresas, por ordem alfabética. | q (texto), setor (slug do setor), distrito (ex.: porto), verificadas=1 (só verificadas), por_pagina |
/companies/{slug} |
Perfil público de uma empresa. | — |
/rfqs |
Pedidos de cotação abertos (dentro do prazo), do mais recente para o mais antigo. | q, setor, distrito, por_pagina |
/rfqs/{slug} |
Detalhe de um pedido de cotação, com a lista de anexos (nome, tamanho e tipo). | — |
/my/rfqs |
Pedidos publicados pela sua empresa, em todos os estados. | por_pagina |
/my/rfqs/{slug}/proposals |
Propostas recebidas num pedido da sua empresa (só a empresa que publicou tem acesso). | por_pagina |
/my/proposals |
Propostas enviadas pela sua empresa, com o pedido associado. | por_pagina |
/my/connections |
Empresas com ligação aceite com a sua empresa. | por_pagina |
/my/offerings |
Catálogo de produtos e serviços da sua empresa (todas as origens: manual, API e conectores). | origem (manual, api, woocommerce…), external_id, publicados (1 ou 0), por_pagina |
/my/offerings/{id} |
Um item do catálogo da sua empresa. | — |
O que cada objeto contém
Empresa: slug, name, nif, verification_status, verified, category (slug, name), district, district_name, city, description, website, email e phone (só no detalhe /companies/{slug} e em /me, nunca nas listas), employees_range, founded_year, rating_avg, reviews_count, logo_url, profile_url.
Pedido de cotação: slug, title, description, requires_nda, nda_accepted, attachments, status (open, closed, awarded, cancelled), is_open, category, district, district_name, quantity, budget, proposals_deadline, delivery_date, proposals_count, company, created_at, url e, só para a empresa dona, budget_visible.
Proposta: id, status (submitted, accepted, rejected, withdrawn), price, delivery_days, message, company ou rfq (conforme o endpoint), created_at, updated_at.
Utilizador (/me): id, name, email, job_title, token_abilities.
As datas seguem o formato ISO 8601 (ex.: 2026-10-08T14:30:00+01:00); os prazos e datas de entrega vêm como AAAA-MM-DD.
Paginação
As listas vêm paginadas:
- os resultados estão em
data; linkstem os URL da primeira, última, anterior e seguinte página;metaindica a página atual (current_page), o tamanho (per_page), o total (total), entre outros.
Use page para mudar de página e por_pagina para o tamanho (de 1 a 50; 20 por omissão).
{
"data": [{ "slug": "metalurgica-do-ave", "name": "Metalúrgica do Ave, Lda.", "verified": true }],
"links": { "first": "...", "last": "...", "prev": null, "next": "...?page=2" },
"meta": { "current_page": 1, "per_page": 20, "total": 134 }
}
Limites de pedidos
- 60 pedidos por minuto por token e, no total, 120 pedidos por minuto por pessoa, somando todos os seus tokens.
- Acima do limite, a API responde
429; os cabeçalhosX-RateLimit-RemainingeRetry-Afterindicam quando pode tentar de novo.
Códigos de erro
| Código | Significado |
|---|---|
401 |
Token em falta, inválido ou revogado. |
403 |
O token não tem permissão para este recurso (por exemplo, propostas de um pedido que não é da sua empresa). |
404 |
Recurso inexistente ou conta sem empresa (endpoints /my/...). Também para oportunidades temporariamente ocultas por denúncias de empresas verificadas (exceto para a própria empresa); estas também não aparecem na lista /rfqs. As empresas nunca são ocultadas automaticamente. |
422 |
Dados inválidos no envio do catálogo (a resposta indica cada campo, ex.: items.3.price), ou POST /my/offerings de um item novo com o catálogo cheio (reason: plan_limit). |
429 |
Limite de pedidos excedido. |
Privacidade: o que nunca é exposto
A API aplica as mesmas regras de privacidade do site:
- orçamentos não públicos —
budgetvemnulla menos que o orçamento seja visível ou o pedido seja da sua empresa; - pedidos com NDA — a descrição, a quantidade e os anexos vêm vazios (
description: null,quantity: null) enquanto a sua empresa não aceitar o acordo no site (ou depois de a empresa compradora mudar o texto do acordo, até o aceitar de novo);nda_acceptedindica o estado. A pesquisa?q=procura só no título destes pedidos; - propostas de outras empresas — só vê as que a sua empresa enviou e as que recebeu nos seus pedidos;
- links de acesso a eventos online, emails pessoais dos utilizadores de outras empresas e tokens nunca são devolvidos.
O email e o telefone devolvidos nas empresas são os contactos públicos que cada empresa publicou no seu perfil, e só vêm no detalhe de uma empresa (/companies/{slug}) e em /me — as listas (/companies, /my/connections e as empresas dentro de pedidos e propostas) não os incluem, para dificultar a recolha de contactos em massa.
Escrever no catálogo de produtos
Disponível em todos os planos, para tokens com a permissão write de um proprietário ou administrador da empresa. Serve para manter o catálogo público (Produtos) igual ao do seu ERP ou loja. Se usa WooCommerce, Shopify, PrestaShop, InvoiceXpress, Odoo ou um feed, não precisa de programar: veja a sincronização automática.
| Método e endpoint | O que faz |
|---|---|
POST /my/offerings/bulk |
Cria ou atualiza até 500 itens por pedido, identificados pelo external_id. Com unpublish_missing: true, despublica os itens da API que não foram enviados. |
POST /my/offerings |
Cria um item, ou atualiza o item da API com o mesmo external_id (responde 201 criado ou 200 atualizado). |
PUT /my/offerings/{id} |
Substitui um item criado pela API (o external_id não muda). |
DELETE /my/offerings/{id} |
Apaga um item criado pela API (responde 204). |
Campos de cada item: external_id (obrigatório, o seu código interno), title (obrigatório, até 150 caracteres), type (product ou service), description, title_en, description_en, sku, price (sem IVA, em euros), currency (só EUR), price_unit, availability (in_stock, on_order, unavailable), url (página do produto no seu site), image_url, category (slug do setor) e published (false para esconder).
Regras:
- Os itens criados no site ou por um conector automático só podem ser lidos pela API; tentar alterá-los dá
403. - As imagens (
image_url) têm de estar num endereço https público e ser JPG, PNG ou WebP até 2 MB; são descarregadas em segundo plano. Se o endereço não mudar, a imagem não é descarregada outra vez. - Os itens despublicados não são apagados: voltam a aparecer se forem enviados de novo.
- Limite do catálogo: no plano Gratuito a empresa tem até 25 produtos e serviços no total (os criados no site, importados por um conector e enviados pela API contam em conjunto); no Empresarial Pro é ilimitado. Os itens são tratados pela ordem em que os envia: os que já existem são sempre atualizados e os novos só são criados enquanto houver lugar. No envio em lote, os restantes vêm em
itemscom"status": "skipped"e"reason": "plan_limit", e a resposta (200) traz o resumo (created,updated,skipped) e o blocolimit(max_items,used,remaining). - Cada envio em lote fica no Registo de sincronizações da empresa (A minha empresa › Produtos e serviços › Sincronização e API).
- Catálogos com mais de 500 itens: envie vários pedidos e, no último, junte
unpublish_missing: trueesync_started_atcom a data e hora do início do primeiro pedido.
curl -X POST https://<endereço do site>/api/v1/my/offerings/bulk \
-H "Authorization: Bearer $PORTUGAL_EMPRESARIAL_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"items": [{"external_id": "PAL-1200", "title": "Palete EUR 1200x800", "price": 12.5, "availability": "in_stock"}]}'
A resposta indica quantos itens foram recebidos, criados, atualizados, iguais (unchanged), despublicados e com erro:
{ "data": { "run_id": 81, "received": 1, "created": 1, "updated": 0, "unchanged": 0, "unpublished": 0, "failed": 0, "errors": [] } }
Passo a passo: ligar a base de dados da empresa ao catálogo
Este guia explica como pôr o catálogo do Portugal Empresarial a acompanhar os produtos que a sua empresa tem na sua própria base de dados (ERP, programa de faturação, loja online ou outra). A ideia é um pequeno programa, no computador ou servidor da empresa, que lê os produtos e os envia pela API, por exemplo uma vez por dia.
Antes de começar: se os produtos estão no WooCommerce, Shopify, PrestaShop, InvoiceXpress ou Odoo, não precisa de programar — use a sincronização automática. Se o seu sistema consegue publicar um ficheiro CSV, JSON ou XML num endereço da internet, também pode usar o Feed por URL, sem programar. Este guia é para os outros casos: uma base de dados própria (MySQL, SQL Server, PostgreSQL…) ou um sistema sem conector.
Passo 1 — Confirmar quem pode ligar
- Tem de ser proprietário ou administrador da empresa no Portugal Empresarial (os membros só podem ler).
- No plano Gratuito, o catálogo tem até 25 produtos e serviços no total; no Empresarial Pro é ilimitado. Os produtos a mais não são criados (ficam com
"reason": "plan_limit"na resposta).
Passo 2 — Criar um token com escrita
- Abra Definições da conta › Tokens da API (/definicoes).
- Dê um nome à integração (ex.: «Catálogo ERP»), marque Permitir escrita e carregue em Criar token.
- Copie o token e guarde-o num local seguro — só é mostrado uma vez.
Guarde o token numa variável de ambiente do computador que vai correr o programa (ex.: PORTUGAL_EMPRESARIAL_TOKEN), nunca dentro do código nem numa folha de cálculo partilhada.
Passo 3 — Testar o token
curl https://<endereço do site>/api/v1/me \
-H "Authorization: Bearer $PORTUGAL_EMPRESARIAL_TOKEN" \
-H "Accept: application/json"
A resposta tem de mostrar a sua empresa e o papel owner ou admin. Um 401 quer dizer que o token está errado ou foi revogado.
Passo 4 — Preparar um acesso só de leitura à base de dados
O programa só precisa de ler os produtos. Peça a quem gere a base de dados um utilizador próprio, só com permissão de leitura na tabela (ou vista) dos produtos. Exemplo em MySQL:
CREATE USER 'catalogo_leitura'@'localhost' IDENTIFIED BY 'uma-palavra-passe-forte';
GRANT SELECT ON erp.produtos TO 'catalogo_leitura'@'localhost';
Assim, mesmo que o programa tenha um erro, nunca altera nem apaga dados do seu sistema.
Passo 5 — Fazer corresponder os campos
Escreva uma consulta (SELECT) que devolva os produtos com os nomes dos campos da API. Correspondência habitual:
| Campo da API | O que é | Exemplo de coluna na sua base de dados |
|---|---|---|
external_id (obrigatório) |
Código único e estável do produto no seu sistema. É por ele que o site sabe se o produto é novo ou já existe. | id, codigo, referencia |
title (obrigatório) |
Nome do produto (até 150 caracteres) | nome, designacao |
description |
Descrição (texto simples) | descricao |
sku |
Referência que os clientes conhecem | referencia, ean |
price |
Preço sem IVA, em euros, com ponto decimal | preco_sem_iva |
price_unit |
Unidade do preço (ex.: unidade, kg, hora) | unidade |
availability |
in_stock (em stock), on_order (sob encomenda) ou unavailable (indisponível) |
calculado a partir do stock |
type |
product (produto) ou service (serviço); por omissão, produto |
tipo |
url |
Página do produto no seu site | url_loja |
image_url |
Imagem num endereço https público (JPG, PNG ou WebP até 2 MB) | url_imagem |
category |
Slug do setor (lista no fim deste guia); vazio = setor da empresa | — |
published |
false para não mostrar no site |
ativo |
title_en, description_en |
Nome e descrição em inglês (opcional) | nome_en, descricao_en |
Exemplo de consulta:
SELECT id AS external_id,
nome AS title,
descricao AS description,
referencia AS sku,
preco_sem_iva AS price,
unidade AS price_unit,
CASE WHEN stock > 0 THEN 'in_stock' ELSE 'on_order' END AS availability,
url_imagem AS image_url,
ativo AS published
FROM produtos;
Passo 6 — Enviar os produtos em lotes
O endpoint POST /api/v1/my/offerings/bulk recebe até 500 produtos por pedido. Para catálogos maiores, envie vários pedidos seguidos e, no último, junte:
"unpublish_missing": true— despublica os produtos que deixaram de existir na sua base de dados (não os apaga: se voltarem, reaparecem);"sync_started_at"— a data e hora em que começou o primeiro pedido (formato ISO, ex.:2026-10-14T03:00:00Z; tem de ser das últimas 24 horas), para o site saber o que foi enviado nesta sincronização.
Exemplo completo em Python (precisa de pip install requests pymysql; para SQL Server use pyodbc e para PostgreSQL psycopg — só muda a ligação):
import os
import time
from datetime import datetime, timezone
from decimal import Decimal
import pymysql
import requests
API = "https://<endereço do site>/api/v1"
HEADERS = {
"Authorization": "Bearer " + os.environ["PORTUGAL_EMPRESARIAL_TOKEN"],
"Accept": "application/json",
}
# 1. Ler os produtos (utilizador só de leitura do passo 4).
db = pymysql.connect(host="localhost", user="catalogo_leitura", password=os.environ["ERP_DB_PASSWORD"],
database="erp", cursorclass=pymysql.cursors.DictCursor)
with db.cursor() as cursor:
cursor.execute("""
SELECT id AS external_id, nome AS title, descricao AS description, referencia AS sku,
preco_sem_iva AS price, unidade AS price_unit,
CASE WHEN stock > 0 THEN 'in_stock' ELSE 'on_order' END AS availability,
url_imagem AS image_url, ativo AS published
FROM produtos
""")
rows = cursor.fetchall()
db.close()
# 2. Converter para o formato da API (sem campos vazios).
def to_item(row):
item = {}
for field, value in row.items():
if value is None or value == "":
continue
if field == "external_id":
value = str(value)
elif field == "published":
value = bool(value)
elif isinstance(value, Decimal):
value = float(value)
item[field] = value
return item
items = [to_item(row) for row in rows]
if not items:
raise SystemExit("A base de dados não devolveu produtos: nada foi enviado.")
# 3. Enviar em lotes de 500; o último despublica o que deixou de existir.
started_at = datetime.now(timezone.utc).isoformat(timespec="seconds")
batches = [items[i:i + 500] for i in range(0, len(items), 500)]
for number, batch in enumerate(batches, start=1):
payload = {"items": batch}
if number == len(batches):
payload["unpublish_missing"] = True
payload["sync_started_at"] = started_at
while True:
response = requests.post(API + "/my/offerings/bulk", json=payload, headers=HEADERS, timeout=120)
if response.status_code == 429: # demasiados pedidos: esperar e repetir
time.sleep(int(response.headers.get("Retry-After", 60)))
continue
break
if response.status_code == 422:
print("Dados inválidos:", response.json().get("errors"))
raise SystemExit(1)
response.raise_for_status()
data = response.json()["data"]
print(f"Lote {number}/{len(batches)}: {data['created']} criados, {data['updated']} atualizados, "
f"{data['unchanged']} iguais, {data['unpublished']} despublicados, {data['failed']} com erro.")
for item in data["items"]:
if item.get("status") in ("failed", "skipped"):
print(" ", item)
Se a sua equipa trabalha noutra linguagem (PHP, C#, Java, Node…), a lógica é a mesma: ler, converter, enviar em lotes de 500 com o cabeçalho Authorization: Bearer … e corpo JSON {"items": [...]}.
Passo 7 — Ler a resposta
Cada lote responde 200 com o resumo e o resultado de cada produto:
| Campo | Significado |
|---|---|
received, created, updated, unchanged, unpublished, failed, skipped |
Quantos produtos foram recebidos, criados, atualizados, ficaram iguais, despublicados, deram erro e ficaram de fora |
items |
Um resultado por produto, pela ordem enviada (created, updated, unchanged, skipped com reason, ou failed com o motivo) |
limit |
max_items, used e remaining — quantos produtos o plano permite e quantos lugares restam |
errors |
Mensagens dos produtos que falharam |
Erros possíveis: 401 (token errado ou revogado), 403 (o token não tem escrita, a pessoa deixou de ser proprietária/administradora, ou tentou alterar um produto criado no site), 422 (um campo inválido — a resposta indica qual, ex.: items.3.price) e 429 (mais de 60 pedidos por minuto; o programa do exemplo espera e repete).
Passo 8 — Agendar a sincronização
Corra o programa automaticamente, por exemplo todas as noites:
-
Linux (cron —
crontab -e), todos os dias às 3h00:0 3 * * * PORTUGAL_EMPRESARIAL_TOKEN=o-seu-token ERP_DB_PASSWORD=… /usr/bin/python3 /opt/catalogo/sincronizar.py >> /var/log/catalogo.log 2>&1 -
Windows — Agendador de Tarefas › Criar Tarefa Básica › Diariamente › Iniciar um programa:
python.execom o argumentoC:\catalogo\sincronizar.py. Defina as variáveis de ambiente do token e da palavra-passe nas definições de sistema do Windows.
Uma sincronização por dia chega para a maioria das empresas; se os preços ou o stock mudam muito, pode correr de hora a hora (cada execução são poucos pedidos).
Passo 9 — Verificar no site
- A minha empresa › Produtos e serviços › Sincronização e API mostra o Registo de sincronizações: cada envio, com data, totais e erros.
- A página pública Produtos e o perfil da empresa mostram os produtos publicados. As imagens aparecem uns segundos depois (são descarregadas em segundo plano).
- Os produtos enviados pela API só se alteram pela API; os criados à mão no site continuam a ser geridos no site.
Setores (category)
| Setor | Slug |
|---|---|
| Agricultura e Agroalimentar | agricultura-e-agroalimentar |
| Comércio e Distribuição | comercio-e-distribuicao |
| Construção e Engenharia | construcao-e-engenharia |
| Consultoria | consultoria |
| Contabilidade e Finanças | contabilidade-e-financas |
| Educação e Formação | educacao-e-formacao |
| Energia e Ambiente | energia-e-ambiente |
| Imobiliário | imobiliario |
| Indústria Transformadora | industria-transformadora |
| Logística e Transportes | logistica-e-transportes |
| Marketing e Design | marketing-e-design |
| Metalomecânica | metalomecanica |
| Outros | outros |
| Saúde | saude |
| Serviços Jurídicos | servicos-juridicos |
| Tecnologia e Software | tecnologia-e-software |
| Têxtil e Vestuário | textil-e-vestuario |
| Turismo e Restauração | turismo-e-restauracao |
Exemplos com curl
Os seus dados e a sua empresa:
curl https://<endereço do site>/api/v1/me \
-H "Authorization: Bearer $PORTUGAL_EMPRESARIAL_TOKEN" \
-H "Accept: application/json"
Empresas verificadas do distrito do Porto que mencionam «moldes»:
curl "https://<endereço do site>/api/v1/companies?q=moldes&distrito=porto&verificadas=1" \
-H "Authorization: Bearer $PORTUGAL_EMPRESARIAL_TOKEN" \
-H "Accept: application/json"
Oportunidades abertas de um setor, 50 por página:
curl "https://<endereço do site>/api/v1/rfqs?setor=<slug-do-setor>&por_pagina=50" \
-H "Authorization: Bearer $PORTUGAL_EMPRESARIAL_TOKEN" \
-H "Accept: application/json"
Propostas recebidas num pedido de cotação da sua empresa:
curl https://<endereço do site>/api/v1/my/rfqs/paletes-eur-ab12cd/proposals \
-H "Authorization: Bearer $PORTUGAL_EMPRESARIAL_TOKEN" \
-H "Accept: application/json"
O slug de um pedido ou de uma empresa é a última parte do endereço da respetiva página (por exemplo, em /oportunidades/paletes-eur-ab12cd, o slug é paletes-eur-ab12cd).
Outras integrações
- Calendário: os eventos podem ser adicionados ao calendário com o ficheiro .ics ou o Google Calendar (ver Eventos).
- Exportação CSV: a lista de inscritos de um evento pode ser exportada (ver Eventos).
- Conta Google: pode entrar no site com a sua conta Google (ver Definições e privacidade).
- Exportação dos seus dados (RGPD): nas definições da conta pode descarregar todos os seus dados pessoais.