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¶
Wysyłka idzie bezpośrednio do magazynu, z pominięciem naszego API.
Content-Typemusi 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¶
{ "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¶
albo, dla załącznika z czatu:
Podaje się dokładnie jedno z dwóch pól.
Odpowiedź:
- 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:
czyli dla awatara ze ścieżką 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 |