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).

StatusTreść
201{ id, shortUrl, url, secretKey, expiresAt }. url to adres docelowy po normalizacji (host w punycode).
400invalid_json, invalid_body albo kod domenowy z tabeli poniżej.
409id_taken
413payload_too_large (ciało większe niż 8 KB)
415unsupported_media_type
429rate_limited
503service_unavailable

GET /api/v1/links/:id

Zwraca adres docelowy linku bez przekierowania.

Wejście. Brak.

StatusTreść
200{ id, url, expiresAt }
404not_found
410expired albo link_disabled
429rate_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>.

StatusTreść
200{ visits, countries: [{ code, visits }], partial }. Z kluczem dodatkowo cities.
403forbidden. Dotyczy każdego nagłówka Authorization, który nie jest poprawnym kluczem tego linku. Nie ma powrotu do widoku publicznego.
404not_found
410expired albo link_disabled
429rate_limited

DELETE /api/v1/links/:id

Usuwa link razem z jego statystykami.

Wejście. Authorization: Bearer <klucz>. Ciało jest ignorowane.

StatusTreść
204Bez treści.
400invalid_key (brak klucza albo zły format)
403forbidden (klucz nie pasuje do linku)
404not_found
429rate_limited

GET /api/v1/stats

Statystyki całego serwisu. Liczby obejmują tylko aktywne linki.

Wejście. Brak.

StatusTreść
200{ links, visits, countries: [{ code, visits }], updatedAt }. Liczby przelicza się raz na dobę i mogą się spóźniać o około 24 godziny.
429rate_limited

Błędy

Każdy błąd ma ten sam kształt:

{ "error": { "code": "id_taken", "message": "customName is already taken" } }
StatusKodZnaczenie
400invalid_jsonCiało żądania nie jest poprawnym JSON-em.
400invalid_bodyCiało nie pasuje do schematu: brak url, pole o złym typie albo nieznane pole.
400invalid_urlurl nie jest poprawnym adresem URL.
400unsupported_schemeDozwolone są tylko http: i https:.
400url_too_longurl ma więcej niż 2048 znaków.
400credentials_not_allowedAdres zawiera dane logowania (user:hasło@).
400own_hostAdres prowadzi do tego serwisu.
400private_targetAdres lokalny, prywatny lub zastrzeżony (IPv4, IPv6, localhost).
400blocked_targetAdres jest innym skracaczem linków albo znajduje się na liście zablokowanych.
400invalid_namecustomName 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.
400name_too_longcustomName jest za długie.
400reserved_namecustomName jest zarezerwowane przez serwis.
400mixed_scriptcustomName miesza alfabet łaciński z innym pismem lub cyframi innego pisma.
400invalid_expiryexpiresAt nie jest istniejącą datą w formacie ISO 8601.
400expiry_in_pastexpiresAt jest w przeszłości.
400expiry_too_farexpiresAt jest dalej niż rok od teraz.
400invalid_keyDELETE bez klucza albo z kluczem w złym formacie.
403forbiddenKlucz nie pasuje do linku.
404not_foundTaki link nie istnieje.
409id_takencustomName jest już zajęte.
410expiredLink wygasł.
410link_disabledLink został wyłączony z powodu nadużycia.
413payload_too_largeCiało żądania ma więcej niż 8 KB.
415unsupported_media_typeContent-Type musi być application/json.
429rate_limitedPrzekroczony limit żądań. Zobacz Retry-After.
500internal_errorBłąd serwera.
503service_unavailableSprawdzenie 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.

TrasaLimit
POST /api/v1/links10 na minutę
DELETE /api/v1/links/:id10 na minutę
GET /api/v1/links/:id120 na minutę, wspólny z przekierowaniami GET /:id
GET /api/v1/links/:id/stats, GET /api/v1/stats60 na minutę

CORS, klucz i nagłówki

  • Odpowiedzi GET mają Access-Control-Allow-Origin: *. Serwer nigdy nie odbija nagłówka Origin i nie zezwala na dane uwierzytelniające.
  • POST i DELETE nie 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 jako Authorization: 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/stats

Usunięcie linku

curl -X DELETE -H 'Authorization: Bearer <klucz>' https://test.dzaj.de/api/v1/links/moj-link

Pole 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 v2Endpoint w v1Co się zmieniło
GET /api/urls/{shortUrl}GET /api/v1/links/:idOdpowiedź { id, url, expiresAt } zamiast { fullLink }.
GET /api/urls/{shortUrl}/infoGET /api/v1/links/:id/statsOdpowiedź { visits, countries, partial }. Miasta (cities) tylko z kluczem. Strona /:id/info przekierowuje (301) na /:id/stats.
POST /api/urlsPOST /api/v1/linksPole 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/:idKlucz 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 po code.