Saltar para o conteúdo
Portugal Empresarial
Secções do manual

API e integrações

A API REST v1 para ligar CRM, ERP ou folhas de cálculo ao Portugal Empresarial — tokens, autenticação, endpoints, paginação, limites e privacidade.

Nesta página

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

Tokens da API nas Definições.
Tokens da API nas Definições.

O acesso à API é feito com tokens pessoais. Cada token pertence a uma pessoa e atua em nome da empresa a que essa pessoa pertence.

  1. Abra Definições da conta › secção Tokens da API (/definicoes).
  2. Em Nome do token, escreva um nome que identifique a integração (entre 2 e 60 caracteres; ex.: «HubSpot», «Primavera»).
  3. Carregue em Criar token.
  4. 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

A documentação da API.
A documentação da API.

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;
  • links tem os URL da primeira, última, anterior e seguinte página;
  • meta indica 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çalhos X-RateLimit-Remaining e Retry-After indicam 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 — budget vem null a 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_accepted indica 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 items com "status": "skipped" e "reason": "plan_limit", e a resposta (200) traz o resumo (created, updated, skipped) e o bloco limit (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: true e sync_started_at com 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": [] } }

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

  1. Abra Definições da conta › Tokens da API (/definicoes).
  2. Dê um nome à integração (ex.: «Catálogo ERP»), marque Permitir escrita e carregue em Criar token.
  3. 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.exe com o argumento C:\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.

Ainda com dúvidas? Pesquise no manual ou contacte-nos.

Tem a certeza?