Przejdź do treści

Pliki — przebieg integracji

Wszystkie ścieżki poniżej są względne wobec /partner/v1/files. Uwierzytelnienie jak w pozostałych endpointach: x-client-id, x-api-key, x-user-id.

Zasada ogólna

Pliki nie przechodzą przez nasze API. Wgrywanie odbywa się bezpośrednio do magazynu, a my jedynie wystawiamy podpisany adres i odnotowujemy fakt wgrania. Dzięki temu duży plik ani wiele równoległych wysyłek nie obciążają naszego backendu, a partner nie musi przesyłać treści dwa razy.

Wgranie pliku to zawsze trzy kroki: poproś o adres, wyślij bajty, potwierdź.

Krok 1 — poproś o adres do wgrania

POST /upload-url
{ "fileName": "wynik-badania.pdf",
  "mimeType": "application/pdf",
  "size": 204800,
  "purpose": "health_record" }

Odpowiedź:

{ "uploadUrl": "https://…", "path": "private/health_record_book/…/wynik-badania.pdf",
  "visibility": "private", "expiresIn": 900 }

purpose określa, do czego plik służy, i to on decyduje o widoczności — nie przekazuje się jej osobno:

purpose Widoczność Zastosowanie
pet_avatar publiczna zdjęcie zwierzaka
user_avatar publiczna zdjęcie użytkownika
health_record prywatna załącznik do wizyty
chat_attachment prywatna plik wysyłany w czacie

Materiały medyczne i załączniki z rozmowy z weterynarzem są niedostępne pod stałym adresem — można je odczytać wyłącznie przez podpisany link (krok 4).

Ograniczenia sprawdzane przed wygenerowaniem adresu: maksymalnie 50 MB oraz dozwolony typ MIME. Akceptujemy obrazy (image/jpeg, image/png, image/gif, image/svg+xml), dokumenty (application/pdf, formaty Word/Excel/OpenDocument, text/csv, text/plain), a także video/mp4, video/quicktime i audio/mpeg. Pełna lista jest w dokumentacji Swagger przy polu mimeType.

Krok 2 — wyślij plik

PUT <uploadUrl>
Content-Type: application/pdf
<surowe bajty>

Wysyłka idzie bezpośrednio do magazynu, z pominięciem naszego API.

  • Content-Type musi być dokładnie taki, jak zadeklarowany w kroku 1 — jest częścią podpisu i rozbieżność spowoduje odrzucenie.
  • Nie należy dołączać żadnych nagłówków uwierzytelniających — podpis jest w adresie.
  • Adres jest ważny 15 minut i uprawnia wyłącznie do zapisania tego jednego pliku.

Krok 3 — potwierdź wgranie

POST /confirm
{ "path": "private/health_record_book/…/wynik-badania.pdf" }
{ "id": "66d1…", "path": "private/health_record_book/…/wynik-badania.pdf",
  "name": "wynik-badania.pdf" }

Ten krok sprawdza, że plik faktycznie znalazł się w magazynie, i rejestruje go w systemie. Bez potwierdzenia plik nie istnieje dla API i nie da się go nigdzie podpiąć.

Wywołanie jest idempotentne — ponowne potwierdzenie tej samej ścieżki zwraca ten sam rekord.

path czy id — zależy od miejsca

To najczęstsze źródło pomyłek. Odpowiedź zawiera oba identyfikatory, ale używa się ich w różnych miejscach:

Gdzie Czego użyć
image_src zwierzaka path
image_src użytkownika path
files w wizycie zewnętrznej path
files w wiadomości czatu id
usunięcie załącznika wizyty Files[].id z odczytu wizyty — patrz niżej

Czat jest jedynym miejscem, które referencuje pliki przez identyfikator.

Trzeci identyfikator: załącznik wizyty

Dopięcie pliku do wizyty tworzy osobny wiersz załącznika, z własnym identyfikatorem. Usuwanie pojedynczego załącznika (DELETE /pets/{petId}/external-appointments/{externalAppointmentId}/files/{fileId}) przyjmuje wyłącznie ten identyfikator — nie id z potwierdzenia wgrania, które kończy się 400 "Validation failed (uuid is expected)".

Znaleźć go można jako Files[].id w odpowiedzi GET .../external-appointments/{id}. W obiegu są więc trzy wartości dotyczące jednego pliku:

Wartość Skąd Do czego
path POST /files/confirm wpisanie pliku do profilu albo do wizyty
id POST /files/confirm załączniki czatu, download-url
Files[].id odczyt wizyty usunięcie załącznika z wizyty

Krok 4 — odczyt pliku

POST /download-url
{ "path": "private/health_record_book/…/wynik-badania.pdf" }

albo, dla załącznika z czatu:

POST /download-url
{ "fileId": "66d1…" }

Podaje się dokładnie jedno z dwóch pól.

Odpowiedź:

{ "url": "https://…", "expiresIn": 3600 }
  • plik publiczny → zwracany jest stały adres, expiresIn: null; można go zapisać i używać bez ograniczeń czasowych
  • plik prywatny → adres podpisany, ważny godzinę

Adres pliku publicznego można złożyć samodzielnie

Dla plików publicznych download-url nie jest konieczne — adres to po prostu adres magazynu doklejony do ścieżki:

https://vetapptemp.s3.eu-central-1.amazonaws.com/ + path

czyli dla awatara ze ścieżką 153e5b…/8f3c…/burek.jpg:

https://vetapptemp.s3.eu-central-1.amazonaws.com/153e5b…/8f3c…/burek.jpg

Adres magazynu jest wspólny dla środowiska testowego i produkcyjnego — różnią się wyłącznie ścieżki, bo każda integracja ma własny prefiks.

Ma to znaczenie przy listach: awatary kilkunastu zwierzaków wystarczy złożyć po swojej stronie, zamiast wykonywać download-url dla każdego z osobna. download-url zostaje przydatne, gdy wolą Państwo nie przechowywać adresu magazynu u siebie.

Plików prywatnych nie da się złożyć w ten sposób — prefiks private/ jest wyłączony z publicznego odczytu przez politykę magazynu, więc taki adres zwróci błąd dostępu niezależnie od poprawności ścieżki. Dla nich zawsze potrzebne jest download-url.

Dostęp do plików prywatnych jest sprawdzany

Podpisany adres powstaje dopiero po weryfikacji, że plik należy do tego użytkownika:

  • załącznik wizyty — musi być podpięty do wizyty zwierzaka, do którego użytkownik ma dostęp
  • załącznik czatu — musi występować w wiadomości z konwersacji, której użytkownik jest uczestnikiem

Ma to praktyczną konsekwencję: plik prywatny trzeba najpierw podpiąć. Wywołanie download-url zaraz po confirm, zanim ścieżka trafi do wizyty lub wiadomości, zakończy się kodem 403.

Obrazy i wideo a wygasanie adresu

Obraz raz pobrany przez przeglądarkę pozostaje widoczny również po wygaśnięciu adresu. Inaczej jest z wideo — odtwarzacz pobiera plik fragmentami, więc przewinięcie po upływie godziny wywoła nowe żądanie z nieaktualnym podpisem. Przy dłuższych sesjach warto odświeżyć adres, korzystając z expiresIn.

Skalowanie obrazów po stronie partnera

Pliki wgrywane tą ścieżką nie są przez nas przetwarzane ani skalowane. Zdjęcie profilowe wgrane w oryginalnej rozdzielczości takie pozostanie i w takiej będzie pobierane. Warto zmniejszyć obrazy przed wysłaniem.

Przykład: zmiana zdjęcia zwierzaka

1) POST /partner/v1/files/upload-url
   { "fileName": "burek.jpg", "mimeType": "image/jpeg",
     "size": 184320, "purpose": "pet_avatar" }
   → { "uploadUrl": "https://…", "path": "8f3c…/burek.jpg",
       "visibility": "public", "expiresIn": 900 }

2) PUT <uploadUrl>
   Content-Type: image/jpeg
   <bajty pliku>

3) POST /partner/v1/files/confirm
   { "path": "8f3c…/burek.jpg" }
   → { "id": "66d1…", "path": "8f3c…/burek.jpg", "name": "burek.jpg" }

4) PATCH /partner/v1/pets/{petId}
   { "image_src": "8f3c…/burek.jpg" }

5) POST /partner/v1/files/download-url
   { "path": "8f3c…/burek.jpg" }
   → { "url": "https://…/8f3c…/burek.jpg", "expiresIn": null }

Krok 5 jest jednorazowy — dla pliku publicznego zwrócony adres jest stały, więc można go zapamiętać i używać wprost w atrybucie src.

Przykład: załącznik do wizyty

1) POST /files/upload-url   { …, "purpose": "health_record" }
2) PUT <uploadUrl>
3) POST /files/confirm      { "path": "private/health_record_book/…" }
4) PATCH /pets/{petId}/external-appointments/{externalAppointmentId}
   { "files": ["private/health_record_book/…"] }       ← dopiero teraz plik jest podpięty
5) POST /files/download-url { "path": "private/health_record_book/…" }
   → { "url": "https://…", "expiresIn": 3600 }

Pole files w wizycie to pełna lista załączników do zachowania. Wysłanie listy bez którejś ze ścieżek usuwa ten załącznik; pominięcie pola files w żądaniu pozostawia załączniki bez zmian.

Najczęstsze przyczyny błędów

Objaw Przyczyna
Odrzucenie przy PUT Content-Type inny niż zadeklarowany w upload-url albo adres starszy niż 15 minut
400 przy upload-url Plik większy niż 50 MB albo niedozwolony typ MIME
404 przy confirm Wysyłka do magazynu się nie powiodła — pliku pod tą ścieżką nie ma
403 przy download-url Plik prywatny niepodpięty jeszcze do wizyty lub wiadomości, albo nienależący do tego użytkownika
400 przy download-url Podano jednocześnie path i fileId albo żadnego z nich
Wideo przestaje działać w trakcie Podpisany adres wygasł — należy pobrać nowy