/auth/login200Entrar com e-mail e senha. Retorna user, accessToken e refreshToken no JSON.
Gerencie o perfil da empresa, produtos e stories pela API, usando a mesma conta do painel.
Começar pela autenticaçãoEsta referência documenta as rotas existentes para integrações de empresas. Não é necessário criar outra conta nem solicitar uma chave de API.
https://daquiuai.unledu.com.br/apiOs exemplos usam Node.js 22 ou superior, com fetch nativo, sem instalar bibliotecas. Salve em um arquivo .mjs para usar await diretamente. Os exemplos seguintes reutilizam baseUrl e accessToken definidos no login. Substitua os IDs pelos valores reais. As operações alteram o mesmo cadastro exibido no painel.
POST /auth/login recebe e-mail e senha. A resposta contém user e accessToken e refreshToken no JSON. A integração não precisa usar cookies.
const baseUrl = 'https://daquiuai.unledu.com.br/api';
const loginResponse = await fetch(baseUrl + '/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: process.env.DAQUI_EMAIL,
password: process.env.DAQUI_PASSWORD,
}),
});
if (!loginResponse.ok) {
throw new Error(await loginResponse.text());
}
const login = await loginResponse.json();
let accessToken = login.accessToken;
let refreshToken = login.refreshToken;
// Use o token nas próximas chamadas.
const userResponse = await fetch(baseUrl + '/auth/me', {
headers: { Authorization: 'Bearer ' + accessToken },
});
if (!userResponse.ok) {
throw new Error(await userResponse.text());
}
const user = await userResponse.json();
console.log(user);Configure DAQUI_EMAIL e DAQUI_PASSWORD no ambiente do seu servidor, com o mesmo e-mail e senha usados no painel. O corpo da chamada contém apenas esses dois campos.
{
"user": {
"id": "ID_USUARIO",
"name": "Seu nome",
"email": "voce@exemplo.com",
"role": "CUSTOMER",
"companyId": "ID_EMPRESA",
"companyRole": "OWNER"
},
"accessToken": "TOKEN_JWT",
"refreshToken": "REFRESH_TOKEN_JWT"
}O accessToken expira em 15 minutos. O token de renovação emitido dura 7 dias. Guarde os dois tokens atualizados a cada renovação.
Para renovar sem reenviar a senha, faça POST enviando o refreshToken no corpo JSON. Não envie o accessToken no lugar dele: os tokens têm finalidades diferentes.
const refreshResponse = await fetch(baseUrl + '/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
if (!refreshResponse.ok) {
throw new Error(await refreshResponse.text());
}
const session = await refreshResponse.json();
accessToken = session.accessToken;
refreshToken = session.refreshToken;const logoutResponse = await fetch(baseUrl + '/auth/logout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
if (!logoutResponse.ok) {
throw new Error(await logoutResponse.text());
}
accessToken = '';
refreshToken = '';O logout revoga os tokens existentes desse usuário, inclusive os usados no painel. Alterações de senha também encerram as sessões. Se o refresh retornar 401, faça login novamente.
Faça a integração no servidor do seu sistema. Não publique senha, tokens ou cookies em JavaScript público, URLs, repositórios ou logs. Para aplicações de terceiros com autorização delegada, esta API ainda não oferece OAuth nem tokens com escopos separados.
Para uma conta de cliente, a empresa é determinada pelo usuário autenticado. Não envie companyId para tentar acessar outra empresa. IDs de itens e stories também precisam pertencer à sua empresa.
OWNER ou MANAGER./admin são exclusivas da administração e não fazem parte desta integração.?companyId=ID_EMPRESA para selecionar explicitamente uma empresa.Uma conta sem empresa vinculada não consegue usar as rotas /manage. Confira user.companyId e user.companyRole no login.
GET /manage/company retorna a empresa. PATCH /manage/company altera somente os campos enviados e retorna o cadastro atualizado. Campos desconhecidos são rejeitados; não envie o objeto completo retornado pelo GET.
const companyResponse = await fetch(baseUrl + '/manage/company', {
method: 'PATCH',
headers: {
Authorization: 'Bearer ' + accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Minha loja',
description: 'Produtos e serviços em Viçosa.',
whatsapp: '5531999999999',
}),
});
if (!companyResponse.ok) {
throw new Error(await companyResponse.text());
}
const company = await companyResponse.json();
console.log(company);| Campo | Tipo | Regra |
|---|---|---|
name | string | Nome, até 160 caracteres. |
description | string | Descrição do negócio, até 5.000 caracteres. |
address | string | Endereço, até 300 caracteres. |
whatsapp | string | Somente dígitos, de 8 a 15; inclua país e DDD. Aceita vazio. |
cityId / categoryId | string | IDs existentes; consulte GET /cities e GET /categories. |
logoUrl / coverUrl | string | null | URL HTTP(S) da imagem; null remove a imagem. Prefira URLs obtidas em /uploads. |
instagramUrl / facebookUrl / tiktokUrl / websiteUrl | string | null | URLs HTTPS. Redes sociais devem usar o domínio da plataforma. |
seoTitle / seoDescription | string | null | Até 120 / 300 caracteres. Vazio ou null restaura a geração automática. |
PUT /manage/company/portfolio recebe {photos: [{imageUrl, caption}]}. Envie até 20 fotos, sem repetir URLs; legendas têm até 160 caracteres. A operação substitui o portfólio inteiro. Para remover todas as fotos, envie {"photos":[]}. Novas imagens devem vir de uploads feitos pelo usuário autenticado.
// imageUrl é a URL retornada pelo upload (veja a seção seguinte).
const portfolioResponse = await fetch(baseUrl + '/manage/company/portfolio', {
method: 'PUT',
headers: {
Authorization: 'Bearer ' + accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({
photos: [{ imageUrl, caption: 'Nosso trabalho' }],
}),
});
if (!portfolioResponse.ok) {
throw new Error(await portfolioResponse.text());
}
const portfolio = await portfolioResponse.json();
console.log(portfolio);PATCH /auth/me altera apenas o nome e o e-mail do usuário. Não use essa rota para alterar a empresa.
Use POST /uploads com multipart/form-data. Envie de 1 a 5 arquivos no campo images, até 5 MiB por arquivo, em JPEG, PNG ou WebP. Não envie campos de texto no multipart. As imagens são processadas e convertidas para WebP.
import { readFile } from 'node:fs/promises';
const file = await readFile('./foto.jpg');
const form = new FormData();
form.append('images', new Blob([file], { type: 'image/jpeg' }), 'foto.jpg');
const uploadResponse = await fetch(baseUrl + '/uploads', {
method: 'POST',
headers: { Authorization: 'Bearer ' + accessToken },
body: form,
});
if (!uploadResponse.ok) {
throw new Error(await uploadResponse.text());
}
const upload = await uploadResponse.json();
const imageUrl = upload.urls[0];
console.log(imageUrl);{
"urls": [
"https://seu-storage.exemplo/users/ID_USUARIO/imagem.webp"
]
}Use as URLs retornadas em produtos, logo, capa e stories. Para enviar várias imagens, repita form.append com o campo images. Não defina Content-Type manualmente: o cliente precisa gerar o boundary.
Stories e novas fotos de portfólio exigem imagens enviadas pelo mesmo usuário autenticado. URLs externas ou imagens enviadas por outra conta podem ser rejeitadas.
POST /manage/items cria um item e retorna seu objeto com id (HTTP 201). PATCH /manage/items/ID_ITEM atualiza campos e retorna o item (HTTP 200).
const product = {
"name": "Camiseta básica",
"description": "Camiseta de algodão, tamanho M.",
"type": "PRODUCT",
"status": "PUBLISHED",
"priceType": "FIXED",
"priceCents": 5990,
"availability": "AVAILABLE",
"condition": "NEW",
"sku": "CAMISETA-M",
"images": [],
"ctaType": "WHATSAPP"
};
const createResponse = await fetch(baseUrl + '/manage/items', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(product),
});
if (!createResponse.ok) {
throw new Error(await createResponse.text());
}
const item = await createResponse.json();
console.log(item.id);O exemplo publica um item com contato por WhatsApp: a empresa precisa ter um número válido. Para criar um serviço, use type: SERVICE. Para salvar sem publicar, use status: DRAFT.
| Campo | Tipo | Regra |
|---|---|---|
name | string | Obrigatório ao criar; até 160 caracteres. |
description | string | Até 5.000 caracteres. |
type | enum | PRODUCT ou SERVICE. |
status | enum | DRAFT ou PUBLISHED. Envie explicitamente para definir a publicação. |
priceType | enum | FIXED, FROM ou ON_REQUEST. |
priceCents | integer | null | Centavos, de 0 a 2.147.483.647. FIXED e FROM exigem preço. |
condition | enum | NEW ou USED. Serviços não podem ser USED. |
availability | enum | AVAILABLE, UNAVAILABLE ou ON_ORDER. |
arrivalBusinessDays | integer | null | 1 a 90; somente PRODUCT com disponibilidade ON_ORDER. |
sku | string | null | Até 100 caracteres. Não pode repetir outro SKU da mesma empresa. |
images | string[] | Até 10 URLs HTTP(S). O array enviado substitui o anterior. |
ctaType | enum | WHATSAPP, EXTERNAL_LINK ou DISPLAY_ONLY. |
ctaUrl | string | null | Obrigatório com EXTERNAL_LINK; não permitido nos outros modos. |
whatsappMessage | string | null | Até 500 caracteres; apenas no modo WHATSAPP. |
optionGroups | array | Até 4 grupos { name, values }. Nome até 30 caracteres; 1–30 valores de até 60. |
variants | array | Até 50 variações. Veja o exemplo abaixo. |
trackStock | boolean | Se true, exige variações e pelo menos uma com estoque controlado. |
{
"optionGroups": [
{
"name": "Cor",
"values": [
"Azul"
]
}
],
"trackStock": true,
"variants": [
{
"name": "Azul",
"sku": "CAMISETA-AZUL",
"priceCents": 5990,
"available": true,
"trackStock": true,
"stockQuantity": 10,
"lowStockThreshold": 2,
"attributes": [
{
"key": "Cor",
"value": "Azul"
}
]
}
]
}Cada variação exige name e available. Estoque controlado exige stockQuantity não negativo; a disponibilidade é calculada pelo estoque. As combinações de opções e os SKUs devem ser únicos. Arrays enviados substituem os arrays existentes, não adicionam elementos automaticamente.
const params = new URLSearchParams({
page: '1', pageSize: '12', type: 'PRODUCT', status: 'PUBLISHED',
});
const listResponse = await fetch(baseUrl + '/manage/items?' + params, {
headers: { Authorization: 'Bearer ' + accessToken },
});
if (!listResponse.ok) {
throw new Error(await listResponse.text());
}
const page = await listResponse.json();
console.log(page.items);const itemId = 'ID_ITEM';
const updateResponse = await fetch(baseUrl + '/manage/items/' + itemId, {
method: 'PATCH',
headers: {
Authorization: 'Bearer ' + accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({ priceType: 'FIXED', priceCents: 6990 }),
});
if (!updateResponse.ok) {
throw new Error(await updateResponse.text());
}
const updatedItem = await updateResponse.json();
console.log(updatedItem);A listagem paginada retorna items, page, pageSize, total e totalPages. pageSize vai de 1 a 48. Use também search para filtrar; envie o parâmetro com codificação de URL. Sem parâmetros de paginação/filtro, a rota mantém a resposta antiga em array.
O cadastro não depende da geração imediata do vetor de pesquisa. A indexação semântica ocorre em segundo plano; o item pode participar da busca textual enquanto aguarda.
const deleteResponse = await fetch(baseUrl + '/manage/items/' + itemId, {
method: 'DELETE',
headers: { Authorization: 'Bearer ' + accessToken },
});
if (!deleteResponse.ok) {
throw new Error(await deleteResponse.text());
}
console.log(await deleteResponse.json());Stories duram 24 horas, com limite de 10 ativos por empresa. A legenda tem até 160 caracteres. A resposta de criação inclui id, imageUrl, caption, createdAt e expiresAt.
const storyResponse = await fetch(baseUrl + '/manage/stories', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({ imageUrl, caption: 'Novidades na loja!' }),
});
if (!storyResponse.ok) {
throw new Error(await storyResponse.text());
}
const story = await storyResponse.json();
console.log(story.id);// Sem corpo JSON: reutiliza a primeira foto do produto.
const productStoryResponse = await fetch(baseUrl + '/manage/stories/from-item/' + itemId, {
method: 'POST',
headers: { Authorization: 'Bearer ' + accessToken },
});
if (!productStoryResponse.ok) {
throw new Error(await productStoryResponse.text());
}
const productStory = await productStoryResponse.json();
console.log(productStory);const storiesResponse = await fetch(baseUrl + '/manage/stories', {
headers: { Authorization: 'Bearer ' + accessToken },
});
if (!storiesResponse.ok) {
throw new Error(await storiesResponse.text());
}
const stories = await storiesResponse.json();
console.log(stories);const storyId = 'ID_STORY';
const removeStoryResponse = await fetch(baseUrl + '/manage/stories/' + storyId, {
method: 'DELETE',
headers: { Authorization: 'Bearer ' + accessToken },
});
if (!removeStoryResponse.ok) {
throw new Error(await removeStoryResponse.text());
}
console.log(await removeStoryResponse.json());Stories de produto exigem um PRODUCT publicado, com imagem, pertencente à sua empresa ativa. O endpoint usa o nome do produto como legenda. Não há rota de edição de story: remova e publique novamente quando necessário.
Os caminhos abaixo são relativos à URL base, que já termina em /api. Exceto login, refresh e logout, envie Authorization: Bearer TOKEN. Refresh e logout recebem {"refreshToken":"SEU_REFRESH_TOKEN"} no corpo JSON, sem precisar de cookie.
/auth/login200Entrar com e-mail e senha. Retorna user, accessToken e refreshToken no JSON.
/auth/refresh200Enviar refreshToken no corpo JSON. Retorna user, accessToken e refreshToken atualizados.
/auth/logout200Enviar refreshToken no corpo JSON para encerrar a sessão e revogar os tokens do usuário.
/auth/me200Consultar o usuário autenticado.
/auth/me200Editar nome e e-mail do usuário (não o perfil da empresa).
/manage/company200Consultar o perfil da empresa vinculada à conta.
/manage/company200Editar o perfil da empresa. Exige OWNER ou MANAGER.
/manage/company/portfolio200Substituir as fotos do portfólio. Exige OWNER ou MANAGER.
/manage/items200Listar produtos e serviços; use page e pageSize para obter uma resposta paginada.
/manage/items201Cadastrar produto ou serviço na empresa da conta.
/manage/items/{id}200Editar parcialmente um produto ou serviço da empresa.
/manage/items/{id}200Excluir um item. Não há recuperação por esta API.
/uploads201Enviar imagens em multipart/form-data, no campo images. Retorna urls.
/manage/stories200Listar stories ativos da empresa.
/manage/stories201Publicar uma imagem enviada pelo próprio usuário. Exige OWNER ou MANAGER.
/manage/stories/from-item/{itemId}201Publicar story usando a primeira foto de um produto. Exige OWNER ou MANAGER.
/manage/stories/{id}200Excluir um story da empresa. Exige OWNER ou MANAGER.
/manage/notifications200Consultar notificações de comentários.
/manage/notifications/count200Consultar unreadCount.
/manage/notifications/read-all200Marcar todas as notificações como lidas.
/manage/notifications/{id}/read200Marcar uma notificação como lida.
{"message":"Token inválido ou expirado"}| Campo | Tipo | Regra |
|---|---|---|
400 | Validação | Confira campos, tipos, enumerações, IDs e limites. |
401 | Autenticação | Token ausente, inválido ou expirado. Tente renovar; se falhar, faça login. |
403 | Permissão | Conta sem vínculo ou função necessária; recurso de outra empresa. |
404 | Não encontrado | Rota ou recurso inexistente/não disponível para esta operação. |
409 | Conflito | Por exemplo: SKU duplicado ou limite de stories ativos. |
413 | Payload grande | Reduza o JSON (limite 128 KiB) ou o arquivo (5 MiB). |
429 | Limite de chamadas | Aguarde antes de tentar novamente quando uma rota aplicar limitação. |
500 / 502 / 503 | Indisponibilidade | Falha interna, de armazenamento, proxy ou provedor. Evite tentativas em sequência. |
Esta página cobre a API atual para perfil, catálogo, imagens, stories e notificações. Recursos de IA e administração não estão incluídos neste guia. Não há webhooks documentados nem garantia de versionamento independente das rotas do painel.