VitrinaAPI

Keep and read the vehicle file

A unit's paperwork, how you read it and what holding it commits you to.

Car dealershipsOnly in car-dealership workspaces.

GET /vehicle-attachments returns a car's paperwork. That means the registration certificate, the consignor's ID card, the signed contract, the certificate of current encumbrances and the invoice. Listing photos travel elsewhere. The API calls these files vehicle attachments.

None of this reaches the public lot. GET /stock never mentions them, no portal receives them, and no signed URL hands them over without authentication. The only door is an authenticated GET, and every read is recorded.

You are receiving other people's personal data

A filename is typed by whoever uploads the file and usually carries someone's name or national id number, like cedula_juan_perez_12345678.jpg. An ID card is, in full, a third party's identity document.

Receiving this makes your platform a data processor for the dealership's personal data under Chile's Ley 21.719: store it under whatever retention rules the dealership sets you, keep it out of your logs and your error-reporting systems, and have a real way to delete it when you are asked. If your integration does not need the paperwork, do not subscribe to vehicle.attachment.created and don't call these endpoints.

The six kinds

kind is a closed list, and it is what decides how a document is handled:

kindWhat it is
padronThe Registro Civil's certificate of registration.
cedulaThe national ID card of whoever consigns or sells.
contratoThe signed contract.
certificado_anotacionesThe certificate of current encumbrances.
facturaThe purchase invoice for the unit.
otroAny other paper in the folder.

They are written like that, unaccented and lowercase, and they are Spanish because they name Chilean documents that have no English equivalent worth inventing. A value outside the list is rejected before the file is touched:

{
  "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"
  }
}

Use otro for a paper that matches none of the five above.

One unit's file

A GET, with the car as a required parameter. Asks for 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 filters the list. vehicle_id is required: there's no listing of every document in the workspace. Without it, the call fails:

{
  "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 says whose data the document carries, the consignor on that ID card. It's the only handle a deletion request has when it arrives with a person's name rather than a file's id.

storage_bucket and storage_path are useless outside the API: storage is private and that path isn't a credential.

Downloading the file

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

The bytes themselves come back. The headers that come with the response are part of what the API promises:

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 for every kind, images included: an identity document downloads rather than previewing in a tab. private, no-store keeps it out of every shared cache.

An id that doesn't exist, or that exists in another workspace, answers the same:

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

Trap

stock:read does not open the file

A key that reads the lot can't download the registration certificate:

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

vehicle_registry:read is a separate permission and no other includes it. Publishable keys, the ones that travel in a browser, can't hold it either.

Uploading a document

Uploading is multipart/form-data, and it asks for 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"

The per-file ceiling is 25 MB, and the accepted types are these: application/pdf, image/jpeg, image/png, image/webp, image/heic, image/heif and image/tiff. A CSV ends here:

{
  "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"
  }
}

The list is closed rather than an image/ prefix with exceptions. image/svg+xml stays off it because an SVG is a container for scripts.

Send subject_contact_id whenever the document belongs to someone. A padron or a factura often doesn't, and the field is omitted. An ID card without it can only be deleted by someone who already knows which file it is.

DELETE /vehicle-attachments/{id} returns 204 and deletes the file permanently. If the deletion fails, the document stays in the listing and you can retry.

The event

Every filed document fires vehicle.attachment.created. This is the one for the ID card above. The subscription had include_data: true, and its owner was a key holding 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"
  }
}

Three things the event doesn't carry:

The bytes are not in it. content_url is, and it's the same authenticated route as above. It's neither signed nor public. It works with a key holding vehicle_registry:read, answers 403 to any other, and the read is recorded like any other.

subject_contact_id is not in it. It's in the file listing, not in the event.

The object key is not in it. storage_bucket and storage_path stay on this side.

And data arrives only because the subscription asked for it and its owner may read the vehicle file. Without include_data, or with an owner lacking vehicle_registry:read, the same delivery carries only the notice: resource with the url above, author and the time. data_omitted says why. Both modes are in Webhooks.

Subscribing to vehicle.attachment.created and verifying the signature is covered in Webhooks. The contract field by field is in Reference · Vehicle attachments, and minting a key with vehicle_registry:read and nothing else is in Authentication and API keys.

On this page