API dzajco v1
Publiczne API JSON do tworzenia, odczytu i usuwania linków. Adres bazowy: https://test.dzaj.de.
Endpointy
POST /api/v1/links
Tworzy link. Wymaga nagłówka Content-Type: application/json.
Wejście. JSON: url (wymagane), customName (opcjonalne), expiresAt (ISO 8601 lub null).
| Status | Treść |
|---|---|
201 | { id, shortUrl, url, secretKey, expiresAt }. url to adres docelowy po normalizacji (host w punycode). |
400 | invalid_json, invalid_body albo kod domenowy z tabeli poniżej. |
409 | id_taken |
413 | payload_too_large (ciało większe niż 8 KB) |
415 | unsupported_media_type |
429 | rate_limited |
503 | service_unavailable |
GET /api/v1/links/:id
Zwraca adres docelowy linku bez przekierowania.
Wejście. Brak.
| Status | Treść |
|---|---|
200 | { id, url, expiresAt } |
404 | not_found |
410 | expired albo link_disabled |
429 | rate_limited |
GET /api/v1/links/:id/stats
Statystyki linku. Bez nagłówka Authorization widok publiczny, z poprawnym kluczem widok właściciela.
Wejście. Opcjonalnie Authorization: Bearer <klucz>.
| Status | Treść |
|---|---|
200 | { visits, countries: [{ code, visits }], partial }. Z kluczem dodatkowo cities. |
403 | forbidden. Dotyczy każdego nagłówka Authorization, który nie jest poprawnym kluczem tego linku. Nie ma powrotu do widoku publicznego. |
404 | not_found |
410 | expired albo link_disabled |
429 | rate_limited |
DELETE /api/v1/links/:id
Usuwa link razem z jego statystykami.
Wejście. Authorization: Bearer <klucz>. Ciało jest ignorowane.
| Status | Treść |
|---|---|
204 | Bez treści. |
400 | invalid_key (brak klucza albo zły format) |
403 | forbidden (klucz nie pasuje do linku) |
404 | not_found |
429 | rate_limited |
GET /api/v1/stats
Statystyki całego serwisu. Liczby obejmują tylko aktywne linki.
Wejście. Brak.
| Status | Treść |
|---|---|
200 | { links, visits, countries: [{ code, visits }], updatedAt }. Liczby przelicza się raz na dobę i mogą się spóźniać o około 24 godziny. |
429 | rate_limited |
Błędy
Każdy błąd ma ten sam kształt:
{ "error": { "code": "id_taken", "message": "customName is already taken" } }| Status | Kod | Znaczenie |
|---|---|---|
400 | invalid_json | Ciało żądania nie jest poprawnym JSON-em. |
400 | invalid_body | Ciało nie pasuje do schematu: brak url, pole o złym typie albo nieznane pole. |
400 | invalid_url | url nie jest poprawnym adresem URL. |
400 | unsupported_scheme | Dozwolone są tylko http: i https:. |
400 | url_too_long | url ma więcej niż 2048 znaków. |
400 | credentials_not_allowed | Adres zawiera dane logowania (user:hasło@). |
400 | own_host | Adres prowadzi do tego serwisu. |
400 | private_target | Adres lokalny, prywatny lub zastrzeżony (IPv4, IPv6, localhost). |
400 | blocked_target | Adres jest innym skracaczem linków albo znajduje się na liście zablokowanych. |
400 | invalid_name | customName ma mieć 3 do 32 liter, cyfr, znaków _ lub -. Nazwa jest zapisywana w postaci NFKC, więc pełnoszerokie litery stają się zwykłymi. |
400 | name_too_long | customName jest za długie. |
400 | reserved_name | customName jest zarezerwowane przez serwis. |
400 | mixed_script | customName miesza alfabet łaciński z innym pismem lub cyframi innego pisma. |
400 | invalid_expiry | expiresAt nie jest istniejącą datą w formacie ISO 8601. |
400 | expiry_in_past | expiresAt jest w przeszłości. |
400 | expiry_too_far | expiresAt jest dalej niż rok od teraz. |
400 | invalid_key | DELETE bez klucza albo z kluczem w złym formacie. |
403 | forbidden | Klucz nie pasuje do linku. |
404 | not_found | Taki link nie istnieje. |
409 | id_taken | customName jest już zajęte. |
410 | expired | Link wygasł. |
410 | link_disabled | Link został wyłączony z powodu nadużycia. |
413 | payload_too_large | Ciało żądania ma więcej niż 8 KB. |
415 | unsupported_media_type | Content-Type musi być application/json. |
429 | rate_limited | Przekroczony limit żądań. Zobacz Retry-After. |
500 | internal_error | Błąd serwera. |
503 | service_unavailable | Sprawdzenie listy zablokowanych adresów nie powiodło się. Spróbuj później. |
Limity
Limity liczone są osobno dla każdego adresu IP w oknie 60 sekund. Po przekroczeniu dostajesz 429 z nagłówkiem Retry-After. Limity działają w każdej lokalizacji osobno, więc to ochrona przed nadużyciem, nie dokładny licznik.
| Trasa | Limit |
|---|---|
POST /api/v1/links | 10 na minutę |
DELETE /api/v1/links/:id | 10 na minutę |
GET /api/v1/links/:id | 120 na minutę, wspólny z przekierowaniami GET /:id |
GET /api/v1/links/:id/stats, GET /api/v1/stats | 60 na minutę |
CORS, klucz i nagłówki
- Odpowiedzi
GETmająAccess-Control-Allow-Origin: *. Serwer nigdy nie odbija nagłówkaOrigini nie zezwala na dane uwierzytelniające. POSTiDELETEnie mają CORS. Wywołuj je z serwera albo z tej samej domeny.- Klucz (
secretKey) dostajesz raz, w odpowiedzi 201. Ma 22 znaki (base64url). Klucze linków przeniesionych z v2 mają 12 znaków (A-Z, a-z, 0-9, _, -) i działają tak samo. Wysyłaj go jakoAuthorization: Bearer <klucz>(nazwa schematu bez rozróżniania wielkości liter). Nigdy w adresie ani w zapytaniu. - Każdy link ma własny klucz. Odpowiedź 201, odpowiedź ze statystykami właściciela i strona wyniku mają
Cache-Control: no-store. - Odpowiedzi JSON mają
Content-Security-Policy: default-src 'none'.
Przykłady
Utworzenie linku
curl -X POST https://test.dzaj.de/api/v1/links \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com/dlugi/adres","customName":"moj-link","expiresAt":null}'Odpowiedź 201:
{
"id": "moj-link",
"shortUrl": "https://test.dzaj.de/moj-link",
"url": "https://example.com/dlugi/adres",
"secretKey": "Zx3k...22 znaki",
"expiresAt": null
}Statystyki publiczne i właściciela
curl https://test.dzaj.de/api/v1/links/moj-link/stats
curl -H 'Authorization: Bearer <klucz>' https://test.dzaj.de/api/v1/links/moj-link/statsUsunięcie linku
curl -X DELETE -H 'Authorization: Bearer <klucz>' https://test.dzaj.de/api/v1/links/moj-linkPole partial w statystykach ma wartość true, gdy najnowszych wejść nie udało się odczytać. Dane z poprzednich dób są wtedy kompletne.
Migracja z v2
Stare adresy /api/urls nie mają aliasu. Adres /dzajapi przekierowuje (301) tutaj. Zmień wywołania według tabeli.
| Endpoint w v2 | Endpoint w v1 | Co się zmieniło |
|---|---|---|
GET /api/urls/{shortUrl} | GET /api/v1/links/:id | Odpowiedź { id, url, expiresAt } zamiast { fullLink }. |
GET /api/urls/{shortUrl}/info | GET /api/v1/links/:id/stats | Odpowiedź { visits, countries, partial }. Miasta (cities) tylko z kluczem. Strona /:id/info przekierowuje (301) na /:id/stats. |
POST /api/urls | POST /api/v1/links | Pole expire to teraz expiresAt (ISO 8601 lub null). Odpowiedź 201: { id, shortUrl, url, secretKey, expiresAt }, bez message. Błędy walidacji mają status 400 zamiast 422, zajęta nazwa 409. Wymagany Content-Type: application/json. |
DELETE /api/urls/{shortUrl} | DELETE /api/v1/links/:id | Klucz idzie w nagłówku Authorization: Bearer <klucz>, nie w ciele { secretKey } i nigdy w adresie. Sukces to 204 bez treści. |
- Błędy mają postać
{ error: { code, message } }zamiast{ error: "..." }. Rozpoznawaj je pocode.