VitrinaAPI

Guardar y leer el expediente

Los papeles de una unidad, cómo se leen y qué obligaciones traen.

AutomotorasSolo en los workspaces de automotoras.

GET /vehicle-attachments devuelve los papeles de un auto. Ahí están el padrón, la cédula del consignante, el contrato firmado, el certificado de anotaciones vigentes y la factura. Las fotos del aviso viajan por otro lado. La API llama a estos archivos vehicle attachments.

Nada de esto sale al lote público. GET /stock no los menciona, ningún portal los recibe y no existe una URL firmada que los entregue sin autenticación. La única puerta es un GET autenticado, y toda lectura queda registrada.

Estás recibiendo datos personales de terceros

Un filename lo escribe la persona que sube el archivo y suele traer el nombre o el RUT de alguien, como cedula_juan_perez_12345678.jpg. Una cédula es, entera, el documento de identidad de un tercero.

Recibir esto convierte a tu plataforma en encargada de tratamiento de los datos personales de la automotora bajo la Ley 21.719: guárdalos con las reglas de retención que la automotora te fije, mantenlos fuera de tus logs y de tus sistemas de errores, y ten una forma real de borrarlos cuando te lo pidan. Si tu integración no necesita los papeles, no te suscribas a vehicle.attachment.created y no llames a estos endpoints.

Los seis tipos

kind es una lista cerrada, y es lo que decide cómo se trata el documento:

kindQué es
padronEl certificado de inscripción del Registro Civil.
cedulaLa cédula de identidad de quien consigna o vende.
contratoEl contrato firmado.
certificado_anotacionesEl certificado de anotaciones vigentes.
facturaLa factura de compra de la unidad.
otroCualquier otro papel de la carpeta.

Se escriben así, sin tildes y en minúscula. Un valor fuera de la lista se rechaza antes de tocar el archivo:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "field_errors": {
      "kind": "Invalid enum value. Expected 'padron' | 'cedula' | 'contrato' | 'certificado_anotaciones' | 'factura' | 'otro', received 'seguro'"
    },
    "requestId": "92547749-0010-4bcd-b52c-14287ba98937"
  }
}

Usa otro para un papel que no corresponde a ninguno de los cinco anteriores.

El expediente de una unidad

Un GET, con el auto como parámetro obligatorio. Pide vehicle_registry:read.

curl "https://api.vitrinadev.com/api/v1/vehicle-attachments?vehicle_id=5d00f5cf-ebe3-4e8b-a456-8d783daed0be" \
  -H "Authorization: Bearer $VITRINA_KEY"
{
  "data": [
    {
      "id": "b0f998b5-eb80-42ea-be16-8356dbbd0643",
      "tenant_id": "00000000-0000-4000-8000-000000000001",
      "vehicle_id": "5d00f5cf-ebe3-4e8b-a456-8d783daed0be",
      "kind": "cedula",
      "storage_bucket": "vehicle-registry",
      "storage_path": "00000000-…/5d00f5cf-…/1fee60f8-…-cedula_consignante.pdf",
      "filename": "cedula_consignante.pdf",
      "mime_type": "application/pdf",
      "byte_size": 610,
      "subject_contact_id": "01a0c68e-4a0c-7bb4-888d-2cd0c3b5ff25",
      "uploaded_by": "20000000-0000-4000-8000-000000000001",
      "uploaded_at": "2026-09-22T00:40:34.958Z",
      "created_at": "2026-09-22T00:40:34.958Z"
    },
    {
      "id": "44173198-bd20-40ea-b08d-8f36dbf5c5df",
      "tenant_id": "00000000-0000-4000-8000-000000000001",
      "vehicle_id": "5d00f5cf-ebe3-4e8b-a456-8d783daed0be",
      "kind": "padron",
      "storage_bucket": "vehicle-registry",
      "storage_path": "00000000-…/5d00f5cf-…/a0d1a414-…-padron_RJKL48.pdf",
      "filename": "padron_RJKL48.pdf",
      "mime_type": "application/pdf",
      "byte_size": 610,
      "subject_contact_id": null,
      "uploaded_by": "20000000-0000-4000-8000-000000000001",
      "uploaded_at": "2026-09-22T00:37:29.063Z",
      "created_at": "2026-09-22T00:37:29.063Z"
    }
  ]
}

?kind=cedula filtra la lista. vehicle_id es obligatorio: no hay un listado de todos los documentos del workspace. Sin él, la llamada falla:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": { "query": [{ "path": "vehicle_id", "message": "Required", "code": "invalid_type" }] },
    "requestId": "1941f0e1-f6e1-4996-b2e1-1dfe9a039914"
  }
}

subject_contact_id dice de quién son los datos del documento, el consignante de esa cédula. Es el único asidero que tiene una solicitud de borrado que llega con el nombre de una persona y no con el id de un archivo.

storage_bucket y storage_path no sirven fuera de la API: el almacenamiento es privado y esa ruta no es una credencial.

Bajar el archivo

curl -O -J "https://api.vitrinadev.com/api/v1/vehicle-attachments/44173198-bd20-40ea-b08d-8f36dbf5c5df/content" \
  -H "Authorization: Bearer $VITRINA_KEY"

Vuelven los bytes mismos. Los headers que acompañan la respuesta son parte de lo que la API promete:

Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''padron_RJKL48.pdf
Cache-Control: private, no-store
Content-Security-Policy: default-src 'none'; sandbox
X-Content-Type-Options: nosniff

attachment para todos los tipos, también para las imágenes: un documento de identidad se descarga y no se previsualiza en una pestaña. private, no-store lo deja fuera de todo caché compartido.

Un id que no existe, o que existe en otro workspace, responde igual:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Adjunto del vehículo no encontrado",
    "requestId": "231dc82d-de98-43c3-8f83-6dcf68f896ba"
  }
}

Trampa

stock:read no abre el expediente

Una key que lee el lote no puede bajar el padrón:

{
  "error": {
    "code": "FORBIDDEN",
    "message": "Missing required scope: vehicle_registry:read",
    "requestId": "112b5532-1839-4010-b9db-d537c3be425b"
  }
}

vehicle_registry:read es un permiso aparte y no está incluido en ningún otro. Las keys publicables, las que viajan en un navegador, tampoco pueden tenerlo.

Subir un documento

Es multipart/form-data y pide vehicle_registry:write:

curl -X POST https://api.vitrinadev.com/api/v1/vehicle-attachments \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -F "vehicle_id=5d00f5cf-ebe3-4e8b-a456-8d783daed0be" \
  -F "kind=cedula" \
  -F "subject_contact_id=01a0c68e-4a0c-7bb4-888d-2cd0c3b5ff25" \
  -F "file=@cedula_consignante.pdf;type=application/pdf"

El tope por archivo son 25 MB, y la lista de tipos aceptados es esta: application/pdf, image/jpeg, image/png, image/webp, image/heic, image/heif e image/tiff. Un CSV termina así:

{
  "error": {
    "code": "UNSUPPORTED_MEDIA_TYPE",
    "message": "unsupported media type: text/csv. An expediente holds application/pdf, image/jpeg, image/png, image/webp, image/heic, image/heif, image/tiff",
    "requestId": "f85f6450-0c5f-43a1-9c79-c9ed8ba4e960"
  }
}

La lista es cerrada y no un prefijo image/ con excepciones. image/svg+xml queda fuera porque un SVG es un contenedor de scripts.

Manda subject_contact_id cada vez que el documento sea de alguien. Un padron o una factura muchas veces no lo son y el campo se omite. Una cédula sin él solo se puede borrar si alguien ya sabe cuál de los archivos es.

DELETE /vehicle-attachments/{id} devuelve 204 y borra el archivo de forma definitiva. Si el borrado falla, el documento sigue en el listado y puedes reintentar.

El evento

Cada documento archivado dispara vehicle.attachment.created. Este es el de la cédula de arriba. La suscripción tenía include_data: true y su dueña era una key con vehicle_registry:read:

{
  "id": "2b69dfff-b3ce-4135-888e-d796f326a11f",
  "type": "vehicle.attachment.created",
  "version": 1,
  "created_at": "2026-09-22T00:40:35.573Z",
  "tenant_id": "00000000-0000-4000-8000-000000000001",
  "resource": {
    "type": "vehicle_attachment",
    "id": "b0f998b5-eb80-42ea-be16-8356dbbd0643",
    "url": "https://api.vitrinadev.com/api/v1/vehicle-attachments/b0f998b5-eb80-42ea-be16-8356dbbd0643/content"
  },
  "author": {
    "kind": "member",
    "id": "20000000-0000-4000-8000-000000000001",
    "name": "dev"
  },
  "data": {
    "id": "b0f998b5-eb80-42ea-be16-8356dbbd0643",
    "vehicle_id": "5d00f5cf-ebe3-4e8b-a456-8d783daed0be",
    "kind": "cedula",
    "filename": "cedula_consignante.pdf",
    "mime_type": "application/pdf",
    "byte_size": 610,
    "uploaded_at": "2026-09-22T00:40:34.958Z",
    "content_url": "https://api.vitrinadev.com/api/v1/vehicle-attachments/b0f998b5-eb80-42ea-be16-8356dbbd0643/content"
  }
}

Tres cosas que el evento no trae:

No vienen los bytes. Viene content_url, que es la misma ruta autenticada de más arriba. No es un enlace firmado y no es público: sirve con una key que tenga vehicle_registry:read, responde 403 a cualquier otra, y la lectura queda registrada como cualquier otra.

No viene subject_contact_id. Está en el listado del expediente, no en el evento.

No viene la llave del objeto. storage_bucket y storage_path se quedan de este lado.

Y data llega solo porque la suscripción lo pidió y su dueña puede leer el expediente. Sin include_data, o con un dueño sin vehicle_registry:read, la misma entrega trae solo el aviso: resource con la url de más arriba, author y la hora. data_omitted dice por qué. Los dos modos están en Webhooks.

Cómo suscribirse a vehicle.attachment.created y verificar la firma está en Webhooks. El contrato campo por campo está en Referencia · Adjuntos de vehículo, y cómo emitir una key con vehicle_registry:read y nada más, en Autenticación y API keys.

En esta página