VitrinaAPI

Centros de ayuda

El portal de documentación de cara al cliente: secciones, artículos multi-idioma y su sincronización con la base de conocimiento.

Beta
Puede cambiar en cualquier momento, con una entrada en el changelog y aviso a quienes la llamaron recientemente — ver versionado.

Descarga la proyección completa de la API pública: openapi.json.

Publicar tu centro de ayuda explica este recurso en prosa, con ejemplos ejecutables.

MétodoRutaQué hace
GET/help-centersList the tenant’s help centers
POST/help-centersCreate a help center
DELETE/help-centers/{id}Delete a help center
GET/help-centers/{id}Get one help center
PUT/help-centers/{id}Update a help center
GET/help-centers/{id}/articlesList articles in a help center
POST/help-centers/{id}/articlesCreate an article
DELETE/help-centers/{id}/articles/{articleId}Delete an article
GET/help-centers/{id}/articles/{articleId}Get one article with its translations
PUT/help-centers/{id}/articles/{articleId}Update an article’s placement or status
POST/help-centers/{id}/articles/{articleId}/archiveArchive an article
GET/help-centers/{id}/articles/{articleId}/feedbackSummarise reader feedback for one article
DELETE/help-centers/{id}/articles/{articleId}/lockRelease the editor lock
POST/help-centers/{id}/articles/{articleId}/lockTake the editor lock on an article
POST/help-centers/{id}/articles/{articleId}/preview-linkMint a signed preview token for an unpublished article
POST/help-centers/{id}/articles/{articleId}/publishPublish an article
POST/help-centers/{id}/articles/{articleId}/resync-kbForce a fresh KB chunk + embed pass on a published article
POST/help-centers/{id}/articles/{articleId}/scheduleSchedule an article to publish later
DELETE/help-centers/{id}/articles/{articleId}/translations/{locale}Delete an article translation
PUT/help-centers/{id}/articles/{articleId}/translations/{locale}Create or replace an article translation
POST/help-centers/{id}/articles/{articleId}/translations/{locale}/generateAI-translate one article into one locale
POST/help-centers/{id}/articles/bulk-translateQueue AI translation of every article into target locales
POST/help-centers/{id}/articles/from-fileCreate an article from an uploaded document
GET/help-centers/{id}/feedbackList reader feedback across the help center
GET/help-centers/{id}/localesList the locales a help center publishes in
POST/help-centers/{id}/localesAdd a locale to a help center
DELETE/help-centers/{id}/locales/{locale}Remove a locale from a help center
POST/help-centers/{id}/locales/{locale}/defaultMake a locale the default
GET/help-centers/{id}/mediaList uploaded media
POST/help-centers/{id}/mediaUpload a media asset (multipart/form-data)
DELETE/help-centers/{id}/media/{mediaId}Delete a media asset
GET/help-centers/{id}/sectionsList the section tree
POST/help-centers/{id}/sectionsCreate a section
DELETE/help-centers/{id}/sections/{sectionId}Delete a section
PUT/help-centers/{id}/sections/{sectionId}Update a section’s slug, parent or position
DELETE/help-centers/{id}/sections/{sectionId}/translations/{locale}Delete a section translation
PUT/help-centers/{id}/sections/{sectionId}/translations/{locale}Create or replace a section translation
POST/help-centers/{id}/sections/reorderReorder sections
GET/help-centers/{id}/translation-coverageTranslation coverage matrix
GET/help-centers/articles/allList articles across every help center in the tenant

GET /help-centers

List the tenant’s help centers

Every help center (portal) in the workspace, with its publish state and locales.

curl https://api.vitrinadev.com/api/v1/help-centers \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "a8a8a8a8-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "slug": "ayuda",
      "name": "Centro de ayuda",
      "brand": null,
      "logo_url": null,
      "primary_color": null,
      "default_locale": "es",
      "supported_locales": [
        "es",
        "en"
      ],
      "custom_subdomain": null,
      "is_published": true,
      "settings": {},
      "created_at": "2026-08-20T12:00:00.000Z",
      "updated_at": "2026-09-10T12:00:00.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers

Create a help center

A new help center starts unpublished — set is_published via PUT once its content is ready. slug is unique per tenant; a collision is a 409.

Cuerpo

CampoTipoObligatorioRestricciones
slugstringpatrón ^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$
namestringmín. 1, máx. 160
brandstringmáx. 120
logo_urlstringuri, máx. 2048
primary_colorstringpatrón ^#[0-9A-Fa-f]{6}$
default_localestringpatrón ^[a-z]{2}(-[A-Z]{2})?$, por defecto "en"
supported_localesstring[]por defecto []
custom_subdomainstringpatrón ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
curl -X POST https://api.vitrinadev.com/api/v1/help-centers \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Centro de ayuda",
    "slug": "ayuda"
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "a8a8a8a8-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "slug": "ayuda",
    "name": "Centro de ayuda",
    "brand": null,
    "logo_url": null,
    "primary_color": null,
    "default_locale": "es",
    "supported_locales": [
      "es",
      "en"
    ],
    "custom_subdomain": null,
    "is_published": false,
    "settings": {},
    "created_at": "2026-08-20T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /help-centers/{id}

Delete a help center

Removes the portal and everything under it — sections, articles and translations.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl -X DELETE https://api.vitrinadev.com/api/v1/help-centers/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}

Get one help center

The full portal row: branding, locales configuration, publish state.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/help-centers/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "id": "a8a8a8a8-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "slug": "ayuda",
    "name": "Centro de ayuda",
    "brand": null,
    "logo_url": null,
    "primary_color": null,
    "default_locale": "es",
    "supported_locales": [
      "es",
      "en"
    ],
    "custom_subdomain": null,
    "is_published": true,
    "settings": {},
    "created_at": "2026-08-20T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

PUT /help-centers/{id}

Update a help center

Partial update — at least one field is required. Nullable fields (brand, logo_url, primary_color, custom_subdomain) are cleared by sending null; omitting them leaves them untouched. is_published is what makes the portal reachable on the public reader.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
slugstringpatrón ^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$
namestringmín. 1, máx. 160
brandstring | nullmáx. 120
logo_urlstring | nulluri, máx. 2048
primary_colorstring | nullpatrón ^#[0-9A-Fa-f]{6}$
default_localestringpatrón ^[a-z]{2}(-[A-Z]{2})?$
supported_localesstring[]
custom_subdomainstring | nullpatrón ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
is_publishedboolean
settingsobject
curl -X PUT https://api.vitrinadev.com/api/v1/help-centers/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "is_published": true,
    "primary_color": "#0EA5E9"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "a8a8a8a8-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "slug": "ayuda",
    "name": "Centro de ayuda",
    "brand": null,
    "logo_url": null,
    "primary_color": "#0EA5E9",
    "default_locale": "es",
    "supported_locales": [
      "es",
      "en"
    ],
    "custom_subdomain": null,
    "is_published": true,
    "settings": {},
    "created_at": "2026-08-20T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}/articles

List articles in a help center

Articles in this ONE portal — see GET /help-centers/articles/all for the tenant-wide list. Filter by section_id or status.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
section_idqueryuuidno
statusquerydraft \published \scheduled \
limitqueryintegerno≥ 1, ≤ 200
curl https://api.vitrinadev.com/api/v1/help-centers/<id>/articles \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "b2b2b2b2-0000-4000-8000-000000000001",
      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
      "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
      "slug": "garantia-de-fabrica",
      "status": "published",
      "position": 0,
      "scheduled_publish_at": null,
      "translations": [
        {
          "locale": "es",
          "title": "Garantía de fábrica",
          "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
        }
      ],
      "created_at": "2026-08-21T12:00:00.000Z",
      "updated_at": "2026-09-10T12:00:00.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles

Create an article

An empty translation.title is allowed on purpose: the editor opens the new article immediately and the title is typed inline. New articles are always created internal — publish them with the publish route.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
slugstringpatrón ^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$
section_idstring | null
positioninteger≥ 0, ≤ 10000
translationobject
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "translation": {
      "locale": "es",
      "title": ""
    }
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "b2b2b2b2-0000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
    "slug": "garantia-de-fabrica",
    "status": "internal",
    "position": 0,
    "scheduled_publish_at": null,
    "translations": [
      {
        "locale": "es",
        "title": "",
        "body": ""
      }
    ],
    "created_at": "2026-08-21T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /help-centers/{id}/articles/{articleId}

Delete an article

Removes the article and every locale’s translation. A published article is un-published and its KB chunks are removed first, so the agent stops quoting it.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
curl -X DELETE https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}/articles/{articleId}

Get one article with its translations

The full article row, every locale’s translation included.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
curl https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "id": "b2b2b2b2-0000-4000-8000-000000000001",
  "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
  "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
  "slug": "garantia-de-fabrica",
  "status": "published",
  "position": 0,
  "scheduled_publish_at": null,
  "translations": [
    {
      "locale": "es",
      "title": "Garantía de fábrica",
      "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
    }
  ],
  "created_at": "2026-08-21T12:00:00.000Z",
  "updated_at": "2026-09-10T12:00:00.000Z"
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

PUT /help-centers/{id}/articles/{articleId}

Update an article’s placement or status

Structure and lifecycle only — the prose lives in translations. Prefer the publish / archive / schedule routes over setting status here: they run the side effects (KB sync on publish, scheduling on schedule) that a bare status write does not.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
slugstringpatrón ^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$
section_idstring | null
positioninteger≥ 0, ≤ 10000
statusdraft \published \scheduled \
visibilitypublic \internal
scheduled_publish_atstring | null
curl -X PUT https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
    "position": 1
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "b2b2b2b2-0000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
    "slug": "garantia-de-fabrica",
    "status": "published",
    "position": 1,
    "scheduled_publish_at": null,
    "translations": [
      {
        "locale": "es",
        "title": "Garantía de fábrica",
        "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
      }
    ],
    "created_at": "2026-08-21T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/{articleId}/archive

Archive an article

Withdraws it from the reader without deleting it; can be published again later.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/archive \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "id": "b2b2b2b2-0000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
    "slug": "garantia-de-fabrica",
    "status": "archived",
    "position": 0,
    "scheduled_publish_at": null,
    "translations": [
      {
        "locale": "es",
        "title": "Garantía de fábrica",
        "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
      }
    ],
    "created_at": "2026-08-21T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}/articles/{articleId}/feedback

Summarise reader feedback for one article

Aggregate of the helpful / not-helpful votes left by readers on the public portal.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
curl https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/feedback \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "helpful": 12,
    "not_helpful": 1,
    "total": 13
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /help-centers/{id}/articles/{articleId}/lock

Release the editor lock

Only clears the lock if the caller holds it. Requires a Supabase user session for the same reason as taking it.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
curl -X DELETE https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/lock \
  -H "Authorization: Bearer $VITRINA_KEY"

Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/{articleId}/lock

Take the editor lock on an article

Advisory lock so two editors do not overwrite each other. Requires a Supabase user session — an sk_* API key has no user behind it and gets 400, since a lock has to be held by someone. The response reports who holds it, so losing the race is a 200, not an error.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/lock \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "locked": true,
    "locked_by": "11111111-0000-4000-8000-000000000001",
    "locked_at": "2026-09-21T10:00:00.000Z"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/{articleId}/preview-link

Mint a signed preview token for an unpublished article

Lets a reviewer open a draft on the public reader without publishing it. The token is valid for 24 hours. locale may be sent in the body or as a ?locale= query parameter; neither is a 400.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
localestringmín. 1
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/preview-link \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "locale": "es"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "token": "ptok_9f1c2e7a8b3d4e5f",
    "locale": "es",
    "expires_in": 86400
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/{articleId}/publish

Publish an article

Makes it visible on the public reader AND syncs its translations into the knowledge base, so the AI agent starts answering from it.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/publish \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "id": "b2b2b2b2-0000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
    "slug": "garantia-de-fabrica",
    "status": "published",
    "position": 0,
    "scheduled_publish_at": null,
    "translations": [
      {
        "locale": "es",
        "title": "Garantía de fábrica",
        "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
      }
    ],
    "created_at": "2026-08-21T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/{articleId}/resync-kb

Force a fresh KB chunk + embed pass on a published article

Publishing already syncs to the KB, so this is the escape hatch for when that sync was lost — after a backfill, an embedding-model change, or an edit made while the background sync was failing. Only published articles can be synced; anything else is a 400.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/resync-kb \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "synced_locales": [
      "es",
      "en"
    ],
    "chunks": 3
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/{articleId}/schedule

Schedule an article to publish later

Moves it to scheduled; a background worker publishes it at scheduled_publish_at, running the same KB sync as a manual publish.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
scheduled_publish_atstringmín. 1
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/schedule \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduled_publish_at": "2026-09-25T13:00:00.000Z"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "b2b2b2b2-0000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
    "slug": "garantia-de-fabrica",
    "status": "scheduled",
    "position": 0,
    "scheduled_publish_at": "2026-09-25T13:00:00.000Z",
    "translations": [
      {
        "locale": "es",
        "title": "Garantía de fábrica",
        "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
      }
    ],
    "created_at": "2026-08-21T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /help-centers/{id}/articles/{articleId}/translations/{locale}

Delete an article translation

Removes this ONE locale’s translation. If the article is published and this was the last translation, the article-level KB entry for that locale is removed too.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
localepathstringpatrón ^[a-z]{2}(-[A-Z]{2})?$
curl -X DELETE https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/translations/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429

PUT /help-centers/{id}/articles/{articleId}/translations/{locale}

Create or replace an article translation

Upsert, and the route the editor autosaves through — which is why an empty title is accepted: it bounces 0-length drafts while the author is still typing. If the article is published, saving re-syncs the KB.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
localepathstringpatrón ^[a-z]{2}(-[A-Z]{2})?$

Cuerpo

CampoTipoObligatorioRestricciones
titlestringmáx. 240
body_markdownstringmáx. 200000
excerptstring | nullmáx. 1000
meta_descriptionstring | nullmáx. 320
seo_titlestring | nullmáx. 240
seo_imagestring | nullmáx. 1024
curl -X PUT https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/translations/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Factory warranty",
    "body": "Every new vehicle carries 3 years or 100,000 km of factory warranty."
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "locale": "en",
    "title": "Factory warranty",
    "body": "Every new vehicle carries 3 years or 100,000 km of factory warranty."
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/{articleId}/translations/{locale}/generate

AI-translate one article into one locale

Synchronous single-cell counterpart to bulk-translate — it returns the finished translation rather than a job count, so it blocks for as long as the model takes. Overwrites any existing translation for that locale.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
articleIdpathuuid
localepathstringpatrón ^[a-z]{2}(-[A-Z]{2})?$
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/<id>/translations/<id>/generate \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "locale": "en",
    "title": "Factory warranty",
    "body": "Every new vehicle carries 3 years or 100,000 km of factory warranty.",
    "generated": true
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/bulk-translate

Queue AI translation of every article into target locales

Asynchronous: enqueues one job per (article, locale) cell that needs work and returns immediately — the translations appear over the following minutes. min_status picks the cells: missing only fills gaps, outdated (the default) also re-translates cells whose source has changed since.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
target_localesstring[]
min_statusmissing \outdated
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/bulk-translate \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target_locales": [
      "en"
    ],
    "min_status": "outdated"
  }'

Ejemplo de respuesta (202)

{
  "enqueued": 4,
  "target_locales": [
    "en"
  ],
  "articles": 4
}

Responde: 202 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/articles/from-file

Create an article from an uploaded document

Extracts the text of a PDF / DOCX / XLSX / plain-text upload server-side and stores it as one article — no object-storage round trip. The slug and title come from the filename. Rejects with 400 when fewer than 20 characters of usable text come out, which is the usual outcome for a scanned image PDF.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/articles/from-file \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -F "file=@/path/to/file.png"

Ejemplo de respuesta (201)

{
  "data": {
    "id": "b2b2b2b2-0000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
    "slug": "garantia-de-fabrica",
    "status": "internal",
    "position": 0,
    "scheduled_publish_at": null,
    "translations": [
      {
        "locale": "es",
        "title": "",
        "body": ""
      }
    ],
    "created_at": "2026-08-21T12:00:00.000Z",
    "updated_at": "2026-09-10T12:00:00.000Z"
  }
}

Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}/feedback

List reader feedback across the help center

Portal-wide feed of reader votes and comments — the per-article view is nested under the article.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/help-centers/<id>/feedback \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "article_id": "b2b2b2b2-0000-4000-8000-000000000001",
      "locale": "es",
      "helpful": true,
      "comment": null,
      "created_at": "2026-09-20T15:00:00.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}/locales

List the locales a help center publishes in

Every locale this portal publishes, with which one is is_default.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/help-centers/<id>/locales \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "locale": "es",
      "is_default": true
    },
    {
      "locale": "en",
      "is_default": false
    }
  ]
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/locales

Add a locale to a help center

Answers 200, not 201 — an oddity of this route, kept for the clients already relying on it. Adding a locale does not translate anything; it opens the column that bulk-translate then fills.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
localestringmín. 2, máx. 16
display_namestring | nullmáx. 120
is_defaultboolean
rtlboolean
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/locales \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "locale": "en"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "locale": "en",
    "is_default": false
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /help-centers/{id}/locales/{locale}

Remove a locale from a help center

Drops the locale and every translation filed under it (sections and articles). The default locale cannot be removed — reassign it first.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
localepathstringmín. 2, máx. 16
curl -X DELETE https://api.vitrinadev.com/api/v1/help-centers/<id>/locales/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/locales/{locale}/default

Make a locale the default

The default locale is what a reader gets with no locale preference, and the source language bulk-translate translates from.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
localepathstringmín. 2, máx. 16
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/locales/<id>/default \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "locale": "en",
    "is_default": true
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}/media

List uploaded media

Every media asset uploaded to this portal’s article editor.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/help-centers/<id>/media \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "a8a8a8a8-4000-4000-8000-000000000001",
      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
      "url": "https://cdn.vitrinadev.com/help-centers/ayuda/garantia.png",
      "content_type": "image/png",
      "size_bytes": 48213,
      "created_at": "2026-09-05T09:00:00.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/media

Upload a media asset (multipart/form-data)

Accepts images, video and documents for the article editor’s media blocks, up to 50 MB. Host anything larger externally and link to it.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/media \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -F "file=@/path/to/file.png"

Ejemplo de respuesta (201)

{
  "data": {
    "id": "a8a8a8a8-4000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "url": "https://cdn.vitrinadev.com/help-centers/ayuda/garantia.png",
    "content_type": "image/png",
    "size_bytes": 48213,
    "created_at": "2026-09-05T09:00:00.000Z"
  }
}

Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /help-centers/{id}/media/{mediaId}

Delete a media asset

Articles that still embed the asset are not rewritten — their image will 404.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
mediaIdpathuuid
curl -X DELETE https://api.vitrinadev.com/api/v1/help-centers/<id>/media/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}/sections

List the section tree

Flat list; nesting is expressed by parent_id, ordering by position.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/help-centers/<id>/sections \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "a9a9a9a9-0000-4000-8000-000000000001",
      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
      "slug": "garantia",
      "parent_id": null,
      "position": 0,
      "created_at": "2026-08-20T12:10:00.000Z",
      "updated_at": "2026-08-20T12:10:00.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/sections

Create a section

A section carries no text of its own — the title and description live in translations, so one is required at create time to name it.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
slugstringpatrón ^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$
parent_idstring | null
positioninteger≥ 0, ≤ 10000
translationobject
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/sections \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "garantia",
    "translation": {
      "locale": "es",
      "title": "Garantía",
      "description": "Todo sobre la garantía de fábrica"
    }
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "a9a9a9a9-0000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "slug": "garantia",
    "parent_id": null,
    "position": 0,
    "created_at": "2026-08-20T12:10:00.000Z",
    "updated_at": "2026-08-20T12:10:00.000Z"
  }
}

Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /help-centers/{id}/sections/{sectionId}

Delete a section

Removes the section and its translations. Articles that were filed under it are not deleted — they lose their section (section_id becomes null).

ParámetroEnTipoObligatorioRestricciones
idpathuuid
sectionIdpathuuid
curl -X DELETE https://api.vitrinadev.com/api/v1/help-centers/<id>/sections/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429

PUT /help-centers/{id}/sections/{sectionId}

Update a section’s slug, parent or position

Structure only — title and description are per-locale, see the translation routes. A section cannot be reparented under itself.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
sectionIdpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
slugstringpatrón ^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$
parent_idstring | null
positioninteger≥ 0, ≤ 10000
curl -X PUT https://api.vitrinadev.com/api/v1/help-centers/<id>/sections/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "position": 1
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "a9a9a9a9-0000-4000-8000-000000000001",
    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
    "slug": "garantia",
    "parent_id": null,
    "position": 1,
    "created_at": "2026-08-20T12:10:00.000Z",
    "updated_at": "2026-08-20T12:10:00.000Z"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /help-centers/{id}/sections/{sectionId}/translations/{locale}

Delete a section translation

Removes this ONE locale. The section itself, and its other locales, are untouched.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
sectionIdpathuuid
localepathstringpatrón ^[a-z]{2}(-[A-Z]{2})?$
curl -X DELETE https://api.vitrinadev.com/api/v1/help-centers/<id>/sections/<id>/translations/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429

PUT /help-centers/{id}/sections/{sectionId}/translations/{locale}

Create or replace a section translation

Upsert — the same call creates the locale or overwrites it.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
sectionIdpathuuid
localepathstringpatrón ^[a-z]{2}(-[A-Z]{2})?$

Cuerpo

CampoTipoObligatorioRestricciones
titlestringmín. 1, máx. 200
descriptionstring | nullmáx. 4000
curl -X PUT https://api.vitrinadev.com/api/v1/help-centers/<id>/sections/<id>/translations/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Garantía",
    "description": "Todo sobre la garantía de fábrica"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
    "locale": "es",
    "title": "Garantía",
    "description": "Todo sobre la garantía de fábrica"
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /help-centers/{id}/sections/reorder

Reorder sections

Atomic: every section_id must belong to this help center, and one that does not fails the whole call rather than reordering the rest.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
orderobject[]
curl -X POST https://api.vitrinadev.com/api/v1/help-centers/<id>/sections/reorder \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "section_ids": [
      "a9a9a9a9-0000-4000-8000-000000000001"
    ]
  }'

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "a9a9a9a9-0000-4000-8000-000000000001",
      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
      "slug": "garantia",
      "parent_id": null,
      "position": 0,
      "created_at": "2026-08-20T12:10:00.000Z",
      "updated_at": "2026-08-20T12:10:00.000Z"
    }
  ]
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/{id}/translation-coverage

Translation coverage matrix

Article × non-default-locale grid, each cell up_to_date | outdated | missing | draft, plus an overall percentage. This is the same computation bulk-translate uses to pick cells, so it doubles as a preview of what a bulk run would do.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/help-centers/<id>/translation-coverage \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "locales": [
      "en"
    ],
    "overall_pct": 75,
    "articles": [
      {
        "article_id": "b2b2b2b2-0000-4000-8000-000000000001",
        "slug": "garantia-de-fabrica",
        "en": "up_to_date"
      }
    ]
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /help-centers/articles/all

List articles across every help center in the tenant

Tenant-wide article list, not scoped to one portal — drives the admin “all articles” view. Unlike the per-help-center list, this route does not validate status: an unknown value simply matches nothing.

ParámetroEnTipoObligatorioRestricciones
statusquerydraft \published \scheduled \
curl https://api.vitrinadev.com/api/v1/help-centers/articles/all \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "b2b2b2b2-0000-4000-8000-000000000001",
      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
      "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
      "slug": "garantia-de-fabrica",
      "status": "published",
      "position": 0,
      "scheduled_publish_at": null,
      "translations": [
        {
          "locale": "es",
          "title": "Garantía de fábrica",
          "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
        }
      ],
      "created_at": "2026-08-21T12:00:00.000Z",
      "updated_at": "2026-09-10T12:00:00.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

En esta página

GET /help-centersPOST /help-centersDELETE /help-centers/{id}GET /help-centers/{id}PUT /help-centers/{id}GET /help-centers/{id}/articlesPOST /help-centers/{id}/articlesDELETE /help-centers/{id}/articles/{articleId}GET /help-centers/{id}/articles/{articleId}PUT /help-centers/{id}/articles/{articleId}POST /help-centers/{id}/articles/{articleId}/archiveGET /help-centers/{id}/articles/{articleId}/feedbackDELETE /help-centers/{id}/articles/{articleId}/lockPOST /help-centers/{id}/articles/{articleId}/lockPOST /help-centers/{id}/articles/{articleId}/preview-linkPOST /help-centers/{id}/articles/{articleId}/publishPOST /help-centers/{id}/articles/{articleId}/resync-kbPOST /help-centers/{id}/articles/{articleId}/scheduleDELETE /help-centers/{id}/articles/{articleId}/translations/{locale}PUT /help-centers/{id}/articles/{articleId}/translations/{locale}POST /help-centers/{id}/articles/{articleId}/translations/{locale}/generatePOST /help-centers/{id}/articles/bulk-translatePOST /help-centers/{id}/articles/from-fileGET /help-centers/{id}/feedbackGET /help-centers/{id}/localesPOST /help-centers/{id}/localesDELETE /help-centers/{id}/locales/{locale}POST /help-centers/{id}/locales/{locale}/defaultGET /help-centers/{id}/mediaPOST /help-centers/{id}/mediaDELETE /help-centers/{id}/media/{mediaId}GET /help-centers/{id}/sectionsPOST /help-centers/{id}/sectionsDELETE /help-centers/{id}/sections/{sectionId}PUT /help-centers/{id}/sections/{sectionId}DELETE /help-centers/{id}/sections/{sectionId}/translations/{locale}PUT /help-centers/{id}/sections/{sectionId}/translations/{locale}POST /help-centers/{id}/sections/reorderGET /help-centers/{id}/translation-coverageGET /help-centers/articles/all