Fejlesztőknek

Dynex App Marketplace - fejlesztői útmutató

Építs saját, a te szervereden futó alkalmazást, amelyet a Dynex-ügyfelek egy kattintással telepítenek: az app a bérlő adataihoz a publikus v3 API-n és webhookokon fér hozzá, saját felülete a Dynex bal menüjében jelenik meg, és ingyenes vagy havidíjas lehet - a havidíj 80 %-a a tiéd.

Az API végpontjainak teljes referenciája külön oldalon van: API dokumentáció. A fejlesztői portál (regisztráció, appok, elszámolás): /developer - előfizetés nem kell hozzá.

Mi az a Dynex-app?

Egy Dynex-app három dolgot kap a platformtól, és mindhármat a te kódod használja a saját szervereden - idegen kód soha nem fut a Dynex szerverein:

  • Adathozzáférés: a bérlő a telepítéskor hozzájárul a kért hatókörökhöz, és az app egy dxk_ API-kulcsot kap, amely pontosan ezekre a hatókörökre jogosít a v3 API-n. Az eseményekről webhookon értesülsz.
  • Saját felület: az app egy vagy több menüpontot kap a Dynex bal menüjének „Alkalmazások” szekciójában; a menüpont a te URL-edet tölti be iframe-ben, aláírt kontextussal (ki a bérlő, ki a felhasználó, milyen téma).
  • Értékesítés: ingyenes vagy havidíjas app. A havidíj a bérlő meglévő Dynex-számlájára kerül, a bevételt havonta osztjuk: 80 % a fejlesztőé, 20 % a Dynexé.

A marketplace-re csak a Dynex tulajdonosának jóváhagyása után kerül ki egy app, és minden új verzió, valamint minden hatókör-bővítés újra jóváhagyásra megy. Amíg nincs jóváhagyva, az appot csak a saját tesztfiókodra telepítheted (sandbox).

Architektúra

RétegHol futMit csinál
Az app szervereNálad (bármilyen stack)OAuth callback, token-csere, API-hívások, webhook-fogadó, a beágyazott oldalak kiszolgálása.
Dynex v3 APIprod.dynex.hu/api/v3REST, Authorization: Bearer dxk_…, hatókör-ellenőrzés, kvóta, idempotencia.
OAuthprod.dynex.hu/api/apps/oauth/*Telepítés hozzájárulási képernyővel; authorization code → kulcs.
BeágyazásA Dynex bal menüje → iframe a te URL-edreAláírt kontextus-token a query-ben, kétirányú postMessage híd.
WebhookDynex → a te webhook_url-edHMAC-SHA256 aláírt POST az eseményekről, újrapróbálással.
SzámlázásDynexHavidíj a bérlő számláján, havi elszámolás a fejlesztői portálon.

Gyorsindítás 10 lépésben

  1. Fejlesztői profil. A fejlesztői portál külön réteg: nem kell hozzá Dynex-előfizetés. Regisztrálj a /developer/register oldalon (cégnév, weboldal, Fejlesztői szerződés), vagy ha már van Dynex-fiókod, lépj be a /developer portálra és hozd létre ott a profilod (kapcsolati e-mail, számlázási adatok a profilban).
  2. Új app. Add meg a nevet, slugot, leírást, az app_url-t (https), a redirect URI-kat (pontos egyezés!), a webhook URL-t, a kért hatóköröket, a beágyazott menüpontokat (path + title) és az árat. Kapsz egy client_id-t és egy client_secret-et - az utóbbi csak egyszer látszik, tárold biztonságosan.
  3. SDK. Töltsd le az SDK-csomagot (dynex-app-sdk.tgz) és telepítsd: npm install ./dynex-app-sdk.tgz (vagy indulj a példa-appból). Töltsd ki a .env-t a client_id / client_secret párral.
  4. OAuth-indítás. Egy „Telepítés a Dynexbe” gomb a buildAuthorizeUrl() által épített címre visz.
  5. Callback. A redirect URI-don a code-ot exchangeAuthorizationCode()-dal kulcsra cseréled, és a választ (kulcs, install_id, tenant_id, embed_secret, webhook_secret) bérlőnként elmented. A Dynex piacteréről indított telepítésnél a code state nélkül, az ELSŐ regisztrált redirect URI-dra érkezik - readMarketplaceCallbackParams()-szal fogadd, és a válasz tenant_id-ját mutasd meg a felhasználónak megerősítésre. A sikeres csere után küldd vissza a bérlőt a Dynexbe: buildMarketplaceReturnUrl({ slug }) = /app/marketplace/<slug>?telepitve=1 - a piactéri oldal ott a „telepítve” visszajelzést mutatja.
  6. Teszt-telepítés. Draft állapotban az app csak olyan bérlőre telepíthető, ahol te tag vagy - ez a sandbox. A piactér listája csak jóváhagyott appot mutat: a draft appot a szerkesztő „Telepítés a tesztfiókra” gombjával (a közvetlen /app/marketplace/<slug> címen) vagy az OAuth-linkről telepítsd a saját fiókodra.
  7. API-hívás. new DynexClient({ accessToken }) - próbáld a me()-t, majd a valódi végpontokat.
  8. Beágyazás. Az app_url + embeds[].path oldalad olvassa a dynex_token paramétert, ellenőrzi (verifyEmbedToken), a böngészőben pedig a createAppBridge() hívja a ready()-t és az autoResize()-t.
  9. Webhook. A telepítéskor a Dynex automatikusan feliratkozik az app default_webhook_events eseményeire a webhook URL-eden; a fogadóban parseWebhookEvent() ellenőrzi az aláírást.
  10. Beküldés. A fejlesztői portálon „Beküldés jóváhagyásra”. A tulajdonos jóváhagyja, visszaküldi megjegyzéssel vagy elutasítja; jóváhagyás után az app megjelenik a marketplace-en.

OAuth-folyamat

Szabványos OAuth 2.0 authorization code folyamat, opcionális PKCE (S256). A kulcs nem jár le, de a bérlő bármikor eltávolíthatja az appot (akkor a kulcs visszavonódik), és hatókör-bővítés csak a bérlő újabb hozzájárulásával lehetséges.

1. Hozzájárulás

GET https://prod.dynex.hu/api/apps/oauth/authorize
  ?response_type=code
  &client_id=dxapp_…
  &redirect_uri=https://app.example.com/oauth/callback   # pontosan a regisztrált
  &state=<véletlen>                                       # CSRF-védelem
  &scope=contacts.readonly reservations.write             # opcionális; kihagyva az app összes hatóköre
  &code_challenge=<base64url(sha256(verifier))>&code_challenge_method=S256   # opcionális PKCE

→ a bérlő bejelentkezik (ha kell), látja a hozzájárulási képernyőt (hatókörök emberi néven, havidíj), megerősít
→ 302 https://app.example.com/oauth/callback?code=dxac_…&state=<ugyanaz>
  vagy elutasításnál: ?error=access_denied&state=…

2. Token-csere

POST https://prod.dynex.hu/api/apps/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=dxac_…&client_id=dxapp_…
&client_secret=dxas_…            # bizalmas app
&code_verifier=…                 # VAGY PKCE (publikus kliens)
&redirect_uri=https://app.example.com/oauth/callback   # KÖTELEZŐ, pontosan az authorize-beli

200 { "access_token": "dxk_…", "token_type": "Bearer", "install_id": "…", "tenant_id": "…",
      "scopes": ["contacts.readonly", "reservations.write"], "embed_secret": "…",
      "webhook_secret": "whsec_…" }   # null, ha az appnak nincs webhook-feliratkozása
4xx { "error": "invalid_grant", "error_description": "Authorization code expired." }
HibakódMikor
invalid_requestHiányzó vagy rossz paraméter; a redirect_uri nincs regisztrálva; nem S256 PKCE.
invalid_clientIsmeretlen client_id, vagy rossz client_secret (és nincs PKCE).
invalid_grantIsmeretlen, már felhasznált vagy lejárt code (10 perc); eltérő redirect_uri; hibás code_verifier; a telepítés már nem aktív.
unauthorized_clientAz app fel van függesztve.
invalid_scopeA kért hatókör ismeretlen, vagy nem része az app jóváhagyott hatóköreinek.
access_deniedA bérlő elutasította a hozzájárulást (a redirect_uri-n érkezik).
server_errorBelső hiba; próbáld újra az engedélyezést.

A code egyszer használatos; párhuzamos beváltásnál csak az egyik nyer. A redirect_uri a token-kérésben is kötelező (nélküle invalid_request). Az embed_secret az appra származtatott titok a beágyazási token helyi ellenőrzéséhez - nem azonos a client_secret-tel; a client_secret újragenerálása ezt is cseréli, a portál az újat egyszer mutatja. A webhook_secret az install webhook-feliratkozásának titka: installonként állandó, újra-engedélyezéskor ugyanaz jön vissza.

Hatókörök

Az app csak azokat a hatóköröket kérheti, amelyeket a beküldéskor megadott és a tulajdonos jóváhagyott; a bérlő a hozzájárulási képernyőn ezeket az emberi neveket látja. Az .readonly végű hatókörök csak olvasnak.

HatókörMit enged a bérlőnek megjelenítveTípus
reservations.readonlyFoglalások megtekintéseolvasás
reservations.writeFoglalások létrehozása és módosításaírás
reservations.manageFoglalások teljes kezelése (törlés, státusz)írás
contacts.readonlyKapcsolatok megtekintéseolvasás
contacts.writeKapcsolatok létrehozása és módosításaírás
contacts.deleteKapcsolatok törléseírás
deals.readonlyÜzletek (pipeline) megtekintéseolvasás
deals.writeÜzletek létrehozása és mozgatásaírás
orders.readonlyRendelések megtekintéseolvasás
menu.readonlyÉtlap megtekintéseolvasás
menu.writeÉtlap szerkesztéseírás
loyalty.readonlyHűségprogram-egyenlegek megtekintéseolvasás
reviews.readonlyÉrtékelések megtekintéseolvasás
webhooks.manageWebhook-feliratkozások kezeléseírás
content.readonlyTartalmak (blog) megtekintéseolvasás
content.writeTartalmak (blog) írásaírás
cameras.writeKamerás vendégszámláló adatok beküldéseírás
conversions.writeKonverziós események küldéseírás

Webhook-események, amelyekre az app alapértelmezetten feliratkozhat (default_webhook_events):

reservation.createdreservation.updatedreservation.cancelledcontact.createdcontact.updatedcontact.deleteddeal.createddeal.stage_changeddeal.wondeal.lostreview.createdreview.replied

Beágyazás és a híd

Az app minden embeds[] bejegyzése egy menüpont a bérlő bal menüjében. Rákattintva a Dynex az /app/apps/<slug>/… oldalon egy teljes magasságú iframe-ben tölti be az app_url + path címet, a query-ben egy aláírt kontextus-tokennel:

https://app.example.com/dashboard
  ?dynex_token=<base64url(payload)>.<base64url(HMAC-SHA256(embed_secret, base64url(payload)))>
  &dynex_theme=light|dark        # az induló téma, hogy az első festés se villanjon

Két token, egy formátum. A query-ben (dynex_token) érkező token PII-mentes: csak az azonosítókat hordozza, a tenantName, userEmail és userRole mezője üres string - az iframe URL az app hozzáférési naplójába és a Refererbe kerül, oda ezek nem valók. A teljes kontextus tokenje a hídon (dynex:context, a dynex:ready után azonnal, majd 4 percenként) és a POST /api/apps/embed/verify válaszában jön - a nevet, e-mailt, szerepet onnan vedd (bridge.onContext).

Payload-mezőJelentés
installIdA telepítés azonosítója (= a token-csere install_id-ja).
tenantIdA bérlő azonosítója.
tenantNameA bérlő neve. A query-tokenben üres (a hídon és a verify-válaszban kitöltve).
userIdA Dynex-felhasználó azonosítója, aki az oldalt nézi.
userEmailA felhasználó e-mail címe. A query-tokenben üres (csak a hídon).
userRoleA felhasználó szerepe a bérlőnél (pl. owner, admin, staff). A query-tokenben üres (csak a hídon).
localeNyelv (jelenleg hu).
themelight | dark - kövesd, hogy az iframe ne üssön el a kerettől.
appSlugAz app slugja.
iatKiadás (unix mp).
expLejárat (unix mp): a kiadástól 5 perc. Ellenőrzéskor 30 mp óraeltérés megengedett.
nonceEgyszeri véletlen - visszajátszás-védelemhez az app oldalán.

Ellenőrzés: helyben a verifyEmbedToken(token, embed_secret)-tel, vagy a Dynexen a POST /api/apps/embed/verify végponton ({ client_id, client_secret, token }{ ok, context, exp, nonce }), ami azt is megnézi, hogy a telepítés még aktív-e. A helyi ellenőrzés mellé ajánlott a rendszeres távoli ellenőrzés (pl. minden új sessionnél), mert az eltávolítást vagy felfüggesztést csak a Dynex látja. A token rövid életű: a betöltés utáni saját sessionödet te kezeled, ne a tokenre építs.

  • A token a query-ben érkezik: PII-mentes, de azonosítókat hordoz (tenantId, userId, installId) és bemutatásra jogosít: ne naplózd (a hozzáférési naplóból maszkold a dynex_token-t), ellenőrzés után azonnal válts saját sessionre, és 302-vel irányíts a token nélküli URL-re, hogy ne maradjon az előzményekben és a Refererben.
  • A kontextus mezői (tenantName, userEmail, …) bérlői bevitel: HTML-be csak escape-elve írd ki, különben tárolt XSS az app originjén.
  • Az egyszeri nonce ellenőrzése nem elvárás - a védelem az aláírás és a 5 perces lejárat. Ha mégis egyszer fogadod el, tartsd a nonce-t csak a lejáratig, és az élő sessiont ne dobd el: a Dynex keret újratöltése ugyanazzal a tokennel jöhet.
  • Iframe-ben a saját sütid csak SameSite=None; Secure-ral megy; Safari a harmadik-fél sütit alapból blokkolja - ott a Storage Access API vagy a hídon 4 percenként érkező friss token a megoldás.

A postMessage híd

Mindkét irány kizárólag a másik fél originjével megy (targetOrigin és event.origin ellenőrzés), és az app csak a közvetlen szülő ablak (event.source === window.parent) üzenetét fogadja el. Az üzenetek JSON-objektumok, a type mező a diszkriminátor.

IrányÜzenetMezőkHatás
app → Dynexdynex:ready-Az app betöltött; a Dynex válaszul dynex:context-et küld.
app → Dynexdynex:resizeheightAz iframe magasságának beállítása (px).
app → Dynexdynex:navigatepathNavigálás a Dynexen belül (pl. /app/reservations); válasz: dynex:navigate-result.
app → Dynexdynex:toastkind, messageÉrtesítés a keretben (success | error | info | warning).
app → Dynexdynex:getContext-Kontextus újrakérése; válasz: dynex:context.
app → Dynexdynex:openExternalurlKülső URL megnyitása új lapon.
Dynex → appdynex:contextcontext, tokenA kontextus (a fenti payload mezői) és egy friss token.
Dynex → appdynex:themethemeTéma-váltás (light | dark).
Dynex → appdynex:navigate-resultok, path, error?A navigálás eredménye.
import { createAppBridge } from "@dynex/app-sdk/browser";

// A Dynex origin a SZERVER konfigjából (pl. <meta name="dynex-origin">), ne a referrerből:
// egy idegen oldalba ágyazott másolatnál a referrer a támadó originje lenne.
const dynexOrigin = document.querySelector('meta[name="dynex-origin"]')?.content;
const bridge = createAppBridge({ dynexOrigin }); // kihagyva: a dynex_origin query-paraméter, majd a referrer - csak https://*.dynex.hu számít, különben prod.dynex.hu
bridge.onContext(({ context }) => render(context)); // context.tenantName, userEmail: escape-elve jelenítsd meg
bridge.onTheme((theme) => document.documentElement.dataset.theme = theme);
bridge.autoResize();                           // ResizeObserver → dynex:resize
bridge.ready();
saveButton.onclick = () => bridge.toast("success", "Elmentve.");
link.onclick = () => bridge.navigate("/app/reservations");

A Dynex az app-oldalakon frame-src 'self' https: CSP-fejlécet küld (fejlesztői buildben http://localhost:* is engedett), a valódi kapu az alkalmazás-réteg: az iframe csak az aktív, jóváhagyott (vagy saját sandbox-) telepítés app_url-jét tölti be, és a keret minden üzenet előtt ellenőrzi, hogy az iframe originje az appé. Élesben ezért a sandbox-telepítéshez is https app_url kell (pl. ngrok / cloudflared alagút). Felfüggesztett app menüpontja eltűnik, és az iframe nem töltődik be. A másik irány a te dolgod: a beágyazott oldalad küldjön Content-Security-Policy: frame-ancestors https://prod.dynex.hu fejlécet, hogy idegen oldal ne ágyazhassa be.

Webhook-aláírás ellenőrzése

A telepítéskor a Dynex a te webhook_url-edre feliratkozást hoz létre az app alapértelmezett eseményeire; a feliratkozás titkát (whsec_…) a token-válasz webhook_secret mezője adja vissza (installonként tárold), illetve a webhooks.create végpont. Minden kézbesítés egy JSON POST:

POST <webhook_url>
Content-Type: application/json
X-Dynex-Event: reservation.created
X-Dynex-Delivery-Id: <uuid>
X-Dynex-Timestamp: <unix mp>
X-Dynex-Signature: sha256=<hex(HMAC-SHA256(secret, "<timestamp>.<nyers törzs>"))>

{ "event": "reservation.created", "deliveryId": "…", "timestamp": "2026-09-15T10:00:00.000Z",
  "tenantId": "…", "data": { "reservation": { … } } }
  • A NYERS törzsből számolj - a JSON újraszerializálása más hash-t ad.
  • Utasítsd el az 5 percnél régebbi időbélyeget (visszajátszás), és hasonlíts időzítés-biztosan.
  • Válaszolj 2xx-szel 10 mp-en belül; hibánál a Dynex 5, 30, 120 és 720 perc múlva újrapróbálja, 20 egymást követő hiba után a feliratkozás kikapcsol.
  • Ugyanaz a deliveryId ritkán kétszer is érkezhet - a feldolgozás legyen idempotens.
import { parseWebhookEvent, WebhookSignatureError } from "@dynex/app-sdk";

app.post("/webhooks", express.raw({ type: "*/*" }), (req, res) => {
  try {
    const event = parseWebhookEvent(req.body, req.headers, install.webhookSecret);
    // event.event, event.tenantId, event.data
    res.status(200).end();
  } catch (e) {
    if (e instanceof WebhookSignatureError) return res.status(401).end();
    throw e;
  }
});

Számlázás és elszámolás

  • Ingyenes app: nincs számlázás; a bérlő a hozzájárulással telepít.
  • Havidíjas app: a telepítéskor a bérlő meglévő Dynex-előfizetéséhez egy „Alkalmazás: <név>” tétel kerül a megadott nettó havi díjjal (Ft). A bérlő a Számlázás oldalán látja; a fizetés a Dynex meglévő motorján megy. Eltávolításnál a tétel a hónap végéig jár. Az app (vagy a fejlesztő) felfüggesztésekor a Dynex a bérlők tételét a folyó hónap végére lemondja - felfüggesztett appért a bérlő nem fizet tovább; visszaállításkor a tétel az eredeti (telepítéskori) árral újraéled. A felfüggesztés hónapjára a fejlesztőnek nem jár részesedés.
  • Elszámolás: minden hónap 1-jén az előző hónapra minden fizetős telepítéshez egy elszámolási sor készül: gross = a hónapra ténylegesen terhelt díj (ha a bérlő nem fizetett vagy lemondott: 0), fejlesztő = gross × 80 % (egész Ft, lefelé kerekítve), a maradék a Dynexé. A részesedést a tulajdonos appra egyedileg módosíthatja.
  • Kifizetés: a fejlesztői portálon látod a havi tételeket; a Dynex a fejlesztői profilban megadott számlázási adatok alapján kéri a számlát, és a kifizetés után „kifizetve” jelöléssel zár. Nincs automatikus utalás.

Beküldés és jóváhagyás

ÁllapotJelentés
draftMunkapéldány: csak a saját (tag) bérlőidre telepíthető - sandbox.
submittedBeküldve; a tulajdonos látja a diffet az előző élő verzióhoz (hatókörök, beágyazások, URL-ek, ár).
approvedÉlő a marketplace-en. A beküldött pillanatkép lett az élő verzió; a további szerkesztés a munkapéldányt írja, újabb beküldésig nem látszik.
rejectedElutasítva megjegyzéssel; javítás után újra beküldhető.
suspendedFelfüggesztve: minden telepítés kulcsa érvénytelen, a menüpontok eltűnnek, az OAuth nem működik, a bérlők havidíja a hónap végére lemondva.

Egyszerre egy függő beküldés lehet. Élő appnál a beküldés nem szünteti meg az élő verziót. Hatókör-bővítés jóváhagyása után a meglévő telepítések a régi hatókörökkel futnak tovább, az új hatókört a bérlő újbóli hozzájárulása (az OAuth-link ismételt megnyitása) adja.

Biztonsági követelmények

  • Minden URL (app_url, redirect_uri, webhook_url) https; fejlesztéshez kizárólag http://localhost engedett.
  • A redirect_uri pontosan egyezik a regisztrált listával - nincs minta, nincs prefix, nincs query-eltérés.
  • A client_secret, a dxk_ kulcs és az embed_secret csak a szervereden él; soha ne kerüljön böngészőbe, mobil-appba vagy repóba. A Dynex a secretet és a kulcsot hashelve tárolja. Kiszivárgás gyanújánál generáld újra a client_secret-et a fejlesztői portálon - ez az embed_secret-et is cseréli (kiszivárgott embed_secret-tel bármely bérlőre hamisítható kontextus-token, ezért a helyi ellenőrzés mellé a távoli /api/apps/embed/verify ellenőrzést is használd).
  • Publikus kliensnél (nincs szerver) PKCE S256 kötelező.
  • A state paramétert véletlenből képezd, a felhasználó sessionjében (HttpOnly süti) tárold, és a callbackben ezzel vesd össze; hiányzó session-értéknél utasítsd el a callbacket (az SDK readCallbackParams-a üres elvárt state-nél null-t ad). A függő state-eket 10 perc után takarítsd.
  • A beágyazási tokent minden betöltéskor ellenőrizd (aláírás + lejárat), ne naplózd, és váltsd saját sessionre; a híd üzeneteit csak a Dynex originről és csak a szülő ablaktól fogadd el; a beágyazott oldal küldjön frame-ancestors CSP-t.
  • A kontextus mezői (tenantName, userEmail, …) és a callback paraméterei idegen bevitel: HTML-be csak escape-elve.
  • Fejlesztői/diagnosztikai végpontot, ami bérlői adatot ad vissza, ne hagyj nyitva (tunnel mögött sem).
  • Webhookot csak érvényes aláírással dolgozz fel; a bérlőt az aláírt törzs tenantId-jából vedd, ne a URL-ből.
  • A kulcs a telepítés hatóköreire korlátozott; ami nem kell, ne kérd. Egy 403 forbidden válasz hiányzó hatókört jelent.
  • Tenant-izoláció nálad is: minden tárolt adatot az install_id / tenant_id alá köss, és eltávolításkor (a kulcs 401-et ad) töröld a bérlő adatait.
  • Kvóta: a kulcsra a v3 API percenkénti és napi keretei érvényesek (X-RateLimit-*, Retry-After).

SDK és példa-app

@dynex/app-sdk (TypeScript, Node 18+): DynexClient (contacts, reservations, availability, loyalty, orders, reviews, webhooks, me; Idempotency-Key támogatás), exchangeAuthorizationCode, buildAuthorizeUrl, createPkcePair, verifyWebhookSignature / parseWebhookEvent, verifyEmbedToken / verifyEmbedTokenRemote, és a böngészős createAppBridge (@dynex/app-sdk/browser).

import { DynexClient, DynexApiError } from "@dynex/app-sdk";

const dynex = new DynexClient({ accessToken: install.accessToken });
const me = await dynex.me();
const { contacts } = await dynex.contacts.list({ updatedSince: lastSyncIso, limit: 100 });
const { reservation, reference } = await dynex.reservations.create(
  { date: "2026-10-01", startTime: "19:00", partySize: 4, customerName: "Kiss Anna", customerPhone: "+36301234567" },
  { idempotencyKey: "order-1234" },
);

A examples/dynex-app-hello egy minimális Express-app: OAuth callback token-cserével (sütihez kötött state + PKCE), beágyazott oldal a híddal (token → saját session → tiszta URL, frame-ancestors CSP, toast és navigálás gombok, a kontextus escape-elve kiírva), aláírás-ellenőrző webhook-fogadó. A README lépésről lépésre végigvisz a localhoston futó teszten (a http://localhost:3999 redirect URI regisztrálható).