Przejdź do głównej zawartości

Przewodnik po API identyfikatorów

Identyfikator łączy fizyczne oznakowanie z rekordem: może to być identyfikator DUST, kod QR, kod kreskowy, symbol Data Matrix, układ NFC lub wydrukowany kod tekstowy. Po powiązaniu skan wykonany w terenie prowadzi do rekordu cyfrowego. Przestrzeń nazw API to /api/v1/tags — starsza nazwa, która przetrwała w ścieżkach i schematach; w tekście tej dokumentacji używamy słowa identyfikator.

Pełne schematy żądań i odpowiedzi: dokumentacja referencyjna API. Wszystkie wyniki, jakie może zwrócić każda operacja — także te, które przychodzą jako błędy HTTP — zebrano w jednej tabeli w artykule Błędy i wyniki skanowania.

OperacjaMetoda i ścieżkaZnaczenie
WyodrębnieniePOST /api/v1/tags/extractPrzetwarza ujęcie DUST na kanoniczny odcisk cyfrowy, bez wiązania
PowiązaniePOST /api/v1/tags/bindWiąże identyfikator z rekordem
IdentyfikacjaPOST /api/v1/tags/identifyWyszukiwanie: który rekord pasuje do tego skanu?
WeryfikacjaPOST /api/v1/tags/verifyPorównuje skan z identyfikatorami konkretnego rekordu
OdłączeniePOST /api/v1/tags/unbindOdłącza identyfikator od jego rekordu
Ustawienie tekstuPOST /api/v1/tags/textZmienia nazwę lub opis powiązanego identyfikatora
AktualizacjaPOST /api/v1/tags/updateCykl życia: prywatność, archiwizacja i przywracanie (wartość i typ są niezmienne)

Identyfikacja a weryfikacja: identyfikacja odpowiada na pytanie „co to jest?” — przeszukuje widoczne dla ciebie rekordy (w zakresie określonym przez searchTeamIds) i zwraca dopasowanie, jeśli istnieje. Weryfikacja odpowiada na pytanie „czy to jest przedmiot, za który się podaje?” — wskazujesz threadId i powiązane z nim identyfikatory kandydujące, a API potwierdza lub zaprzecza. Weryfikacji używaj przy decyzjach o autentyczności, identyfikacji — do wyszukiwania.

Punkty końcowe skanowania przyjmują multipart/form-data, a postać pola data zależy od typu identyfikatora:

tagTypedataŹródło
DUSTObraz — binarna część pliku lub adres URL danych base64 (data:image/jpeg;base64,…)Optyczne ujęcie DUST ze skanera
QR, BAR_CODE, DATA_MATRIX, NFCZdekodowana zawartość w postaci tekstu (lub szesnastkowe ID NFC)Dowolny skaner symboli
TEXTWydrukowany kod czytelny dla człowieka, w takiej postaci, w jakiej odczytuje go osobaWpisanie z klawiatury lub rejestracja etykiety

Ujęcie DUST to zdjęcie identyfikatora, a nie zdekodowana wartość — odcisk cyfrowy wyodrębnia serwer. Ujęcia pochodzą ze sprzętu do skanowania DUST: zobacz Integracja z DUST Go, aby dowiedzieć się o przechwytywaniu na urządzeniach mobilnych, oraz React Scanner — gotowy komponent internetowy obsługujący wszystkie tryby.

W treściach multipart pola strukturalne (options, tags, searchTeamIds) przekazuje się jako ciągi JSON.

TEXT to czytelny dla człowieka kod wydrukowany na przedmiocie lub etykiecie — numer seryjny, taki jak AB00017. Nie ma tu symbolu do zdekodowania, więc wartość jest wpisywana (lub pochodzi z zapisu rejestracji etykiety) i przechowywana dokładnie w takiej postaci, w jakiej ją wprowadzono. Ponieważ odczytuje ją człowiek, TEXT jest jedynym typem, który platforma dopasowuje bez rozróżniania wielkości liter: identyfikacja lub weryfikacja z wartością ab00017 znajduje powiązany identyfikator AB00017. Wszystkie pozostałe typy są dopasowywane bajt po bajcie.

Podobnie jak wartości QR, kodów kreskowych, Data Matrix i NFC kod tekstowy można skopiować i sam w sobie nie zapewnia unikatowości — ten sam kod może zgodnie z przeznaczeniem występować na kilku rekordach albo na każdej etykiecie rolki. Powtórzona wartość nie jest nigdy odrzucana; rolka po prostu zgłasza powtórzenia jako ostrzeżenie.

Wyodrębnianie przetwarza ujęcie na kanoniczny odcisk cyfrowy i zwraca jego jakość — przydaje się do sprawdzenia ujęcia przed rejestracją albo do przygotowania powiązania:

Okno terminala
curl -fsS "$APID_URL/api/v1/tags/extract" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-F "data=@scan.jpeg" \
-F 'options={"enrollmentSessionId":"3d5e…"}'

Odpowiedź ma postać { id, qualityScore, annotatedImage?, forensics?, scan? } — id to ID odcisku cyfrowego, który możesz później powiązać bez ponownego przesyłania obrazu (patrz niżej). options zawiera też metadane ujęcia (urządzenie, optyka, geolokalizacja), które platforma przechowuje razem ze skanem.

Każda operacja przesyłająca obraz — wyodrębnianie, powiązanie, identyfikacja, weryfikacja i analiza manipulacji — zwraca obiekt scan wskazujący, co zostało zapisane:

{ "scanId": "…", "fingerprintId": "…", "dustId": "…" }
  • scanId jest zawsze obecne, gdy obraz został zapisany. To stała tożsamość ujęcia i wartość, którą warto zachować, jeśli zapisujesz operacje skanowania po swojej stronie.
  • fingerprintId jest obecne, gdy wyodrębnianie się powiodło (w przeciwnym razie null).
  • dustId jest obecne, gdy operacja zwróciła ci DUST: identyfikator utworzony przez powiązanie, potwierdzony przez weryfikację lub wskazany przez identyfikację. Ma wartość null w przypadku niezgodności, braku dopasowania, wyodrębniania, analizy manipulacji (identyfikator podajesz tam sam, a nie jest on wynikiem rozpoznania obrazu) oraz identyfikacji, która zwróciła kilku kandydatów (każdy kandydat zawiera własny identyfikator).

Niezgodność przy weryfikacji i brak dopasowania przy identyfikacji zachowują dotychczasowy status i kod błędu, a ten sam raport skanu przekazują w detail.scan — skan został zapisany, mimo że wynik był negatywny. Tak samo jest z ujęciem odrzuconym z powodu jakości — za mało lub brak użytecznych punktów kluczowych — niezależnie od kodu błędu, jaki zgłasza dla niego operacja (/tags/extract odpowiada SCAN_EXTRACTION_FAILURE; identyfikacja zwraca SCAN_LOW_KEYPOINTS / SCAN_NO_KEYPOINTS; weryfikacja odpowiada swoim zwykłym IDENTIFIER_VERIFY_FAILED): obraz zostaje zachowany, a jego raport skanu ma fingerprintId: null, ponieważ nie wyodrębniono z niego niczego użytecznego. Tylko obraz, którego platforma w ogóle nie mogła zdekodować, nie jest zapisywany i nie ma raportu skanu.

POST /api/v1/tags/bind przyjmuje trzy postacie, rozróżniane przez tagType i ładunek:

Okno terminala
curl -fsS "$APID_URL/api/v1/tags/bind" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-F "threadId=$THREAD_ID" \
-F "tagType=DUST" \
-F "tagDescription=Inbound receiving scan" \
-F "data=@scan.jpeg" \
-F 'options={"enrollmentSessionId":"3d5e…"}'

options.enrollmentSessionId jest opcjonalne przy powiązaniu DUST. Podaj UUID wygenerowany po stronie klienta — ten sam przez cały przebieg — gdy kilka ujęć stanowi całość, na przykład gdy stanowisko rejestracji przetwarza partię albo gdy jeden przedmiot jest uchwycony pod kilkoma kątami; platforma grupuje wtedy te skany w ramach tej sesji. W przypadku jednorazowego powiązania pomiń to pole całkowicie. Powiązania z obrazem DUST mogą też zwrócić ujęcie z adnotacjami, jeśli ustawisz options.returnAnnotatedImage: true.

Jeśli zeskanowany identyfikator należy do etykiety, której właścicielem jest zespół rekordu (patrz Etykiety), powiązanie nie tworzy samodzielnego identyfikatora. Wiąże całą etykietę: każdy aktywny identyfikator członkowski zostaje powiązany z rekordem w jednej operacji, a odpowiedź zawiera label (etykietę, jej rolkę i pozycję) oraz boundTags (wszystkie powiązane identyfikatory członkowskie) obok zwykłego pola tag, które wskazuje zeskanowany identyfikator członkowski. Przekaż activateLabel: true, aby w ramach powiązania uczynić również identyfikatory DUST etykiety identyfikowalnymi; jest to opcjonalne. Etykieta powiązana już z innym rekordem zwraca IDENTIFIER_ALREADY_BOUND z detail.compositeTagId.

Okno terminala
curl -fsS "$APID_URL/api/v1/tags/identify" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-F "tagType=DUST" \
-F "data=@scan.jpeg" \
-F 'searchTeamIds=["'"$TEAM_ID"'"]'

Identyfikacja przyjmuje też ładunki z wartością (tagType równy QR/BAR_CODE/DATA_MATRIX/NFC ze zdekodowanym data albo TEXT z wydrukowanym kodem, dopasowywanym bez rozróżniania wielkości liter) lub samo ID identyfikatora (tagType: "ANY" z tagId).

Trafienie zwraca 200 z dopasowanym identyfikatorem i jego rekordem. Brak trafienia to status błędu, a nie 200 z pustym wynikiem: ostateczny brak dopasowania to 404 IDENTIFIER_NOT_FOUND, a wyszukiwanie, którego nie udało się ukończyć, to 503 SCAN_SEARCH_INCOMPLETE — inna sytuacja, której nie wolno pokazywać operatorowi jako „nie znaleziono”. Oba przypadki zawierają raport skanu w detail.scan.

Kanoniczna tabela wszystkich ośmiu wyników — zidentyfikowany rekord, kilku kandydatów, niepowiązana etykieta, brak dopasowania, niekompletne wyszukiwanie, niejednoznaczne dopasowanie, odrzucone ujęcie, niepowiązany identyfikator — wraz ze statusem, kodem i właściwą reakcją klienta dla każdego z nich znajduje się w artykule Błędy i wyniki skanowania → Kanoniczne wyniki identyfikacji. Rozgałęziaj logikę według code, a gdy potrzebujesz dokładniejszego rozróżnienia, odczytaj detail.outcome (no_match, search_incomplete, ambiguous, quality_reject).

Każdy zwrócony identyfikator należący do etykiety zawiera tag.label (swoją etykietę, rolkę i pozycję). Gdy skan pasuje do identyfikatora członkowskiego niepowiązanej etykiety z zasobów aktywnego zespołu, wynikiem jest { type: "label", label: { label, tags } }: nie ma jeszcze rekordu, ale jest etykieta i jej identyfikatory członkowskie, więc klient może zaproponować jej powiązanie (patrz Etykiety).

searchTeamIds to tablica JSON z UUID zespołów (w treściach multipart — ciąg JSON). Jeśli go pominiesz, identyfikacja przeszuka dokładnie jeden zespół: ten wskazany przez Dust-Ctx-Team-Id, który domyślnie wskazuje zespół główny organizacji.

Wartości ID nie mogą być dowolne. Zespoły z tej samej organizacji, do których należysz, są zawsze w zakresie; zespół organizacji partnerskiej jest osiągalny tylko przez aktywne połączenie, które pozwala na przepływ jego danych do ciebie. Wszystkie inne są po cichu usuwane z zakresu, zamiast powodować niepowodzenie żądania, więc zakres, który wygląda na szeroki, może w praktyce przeszukiwać wąsko. Zamiast wpisywać prawidłowe wartości ID na sztywno, ustal je:

  • GET /api/v1/teams — zespoły w twojej organizacji, do których należą twoje poświadczenia ({ teams: [{ teamId, orgId, name, … }], total }).
  • GET /api/v1/teams/connected — zespoły partnerskie, które możesz przeszukiwać, w postaci rekordów połączeń wskazujących dwa połączone zespoły.

Weryfikacja to podstawowa operacja uwierzytelniania: mając świeży skan, threadId i kandydujące identyfikatory tags już powiązane z tym rekordem, kończy się powodzeniem, jeśli pasuje którykolwiek kandydat.

tags jest wymagane i jest tablicą obiektów — każdy w postaci { "tagId": "…", "tagType": "…" } — a nie tablicą ciągów z ID. W treści multipart przesyła się je jako ciąg JSON:

Okno terminala
curl -fsS "$APID_URL/api/v1/tags/verify" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-F "threadId=$THREAD_ID" \
-F "tagType=DUST" \
-F "data=@scan.jpeg" \
-F 'tags=[{"tagId":"'"$TAG_ID"'","tagType":"DUST"}]'
const form = new FormData();
form.set("threadId", threadId);
form.set("tagType", "DUST");
form.set("data", scanBlob);
form.set("tags", JSON.stringify([{ tagId, tagType: "DUST" }]));

Wartości tagId to identyfikatory już powiązane ze sprawdzanym rekordem. Odczytaj je z rekordu: GET /api/v1/threads/{thread_id} zwraca je w thread.tags, każdy z jego tagId i tagType. Typowa weryfikacja pobiera więc rekord, filtruje jego identyfikatory do typu, który właśnie przechwycono, i wysyła je jako listę kandydatów:

const record = await getThread(threadId); // GET /api/v1/threads/{thread_id}
const candidates = (record.thread.tags ?? [])
.filter((tag) => tag.tagType === "DUST")
.map((tag) => ({ tagId: tag.tagId, tagType: tag.tagType }));

Wysłanie identyfikatora, który nie jest powiązany z tym rekordem, powoduje niepowodzenie weryfikacji, a nie dopasowanie czegoś innego.

Liczba kandydatów zmienia postać odpowiedzi — to najczęstszy błąd integracji w tym miejscu:

Długość tagsDopasowanieBrak dopasowania
Dokładnie jeden200 z { tag, scan? }IDENTIFIER_VERIFY_FAILED (HTTP 500), raport skanu w detail.scan
Dwa lub więcej200 z { success: true, verifiedTag, attemptedCount, failedCount, scan? }200 z { success: false, attemptedCount, failedCount, error, scan? }

Nieudana weryfikacja z wieloma kandydatami to więc udane wywołanie HTTP zawierające success: false. Nigdy nie traktuj response.ok jako dowodu autentyczności — odczytuj success za każdym razem, gdy wysyłasz więcej niż jednego kandydata. Zobacz Błędy i wyniki skanowania → Wyniki weryfikacji.

To zwykłe punkty końcowe JSON; wszystkie wymagają zarówno tagId, jak i threadId rekordu, z którym powiązany jest identyfikator:

  • POST /api/v1/tags/text — ustawia name i/lub description.
  • POST /api/v1/tags/update — ustawia name, description, isPrivate i archivedAt (znacznik czasu ISO archiwizuje identyfikator; null go przywraca). Wartość i typ identyfikatora są niezmienne — zamiast tego powiąż go ponownie.
  • POST /api/v1/tags/unbind — odłącza identyfikator od rekordu.

W ramach /api/v1/tamper analiza manipulacji porównuje świeży skan identyfikatora DUST z ujęciem referencyjnym wykonanym przy powiązaniu i zapisuje wyniki pomiarów. API zwraca wyłącznie wyniki pomiarów i dowody — w całym interfejsie nie ma żadnej liczby podsumowującej, przedziału, progu ani pola wyniku ustalanego przez platformę, a alignmentOutcome informuje wyłącznie o tym, czy oba skany w ogóle dało się porównać (jeśli nie, wyniki pomiarów są nieporównywalne, co nie jest żadnym stwierdzeniem na temat identyfikatora).

OperacjaMetoda i ścieżka
Uruchomienie analizyPOST /api/v1/tamper/analyses — w postaci formularza: threadId, tagId i dokładnie jedno z pól data lub queryFingerprintId
Zapisanie obserwacjiPOST /api/v1/tamper/observations — { analysisId, result }
Lista analiz rekorduGET /api/v1/tamper/analyses?threadId=… (opcjonalnie tagId, limit)
Pobranie jednej analizyGET /api/v1/tamper/analyses/{analysis_id}
Pobranie mapy bitowej wynikuGET /api/v1/tamper/analyses/{analysis_id}/artifacts/{name}

Uruchomienie analizy wymaga treści multipart/form-data lub application/x-www-form-urlencoded z polami threadId, tagId oraz dokładnie jednym z pól:

  • data — sam skan DUST jako plik lub obraz zakodowany w base64. Usługa wyodrębnia go za ciebie.
  • queryFingerprintId — ID odcisku cyfrowego uzyskane wcześniej z POST /api/v1/tags/extract (patrz wyżej), jeśli wyodrębnianie wykonano osobno.

Wysłanie obu pól albo żadnego zostaje odrzucone. W obu przypadkach zwykłe ujęcie DUST jest prawidłowymi danymi wejściowymi — dla analizy manipulacji nie ma osobnej ścieżki przechwytywania. Jeśli przesłanego skanu nie da się odczytać, żądanie kończy się niepowodzeniem i analiza nie zostaje zapisana.

Obserwacja dotycząca manipulacji to jedyny wniosek, jaki przechowuje platforma, a jej autorem jest człowiek: result przyjmuje jedną z wartości consistent, expected, inconsistent lub unknown, nie ma wartości domyślnej i jest wymagane. expected oznacza normalne zużycie, typowe dla przeznaczenia i podłoża identyfikatora. Obserwacje są niezmienne i przypisane do autora; nowa nigdy nie zastępuje wcześniejszej, a odczyty zwracają całą serię (observations, od najnowszej), a nie jeden bieżący wynik. We własnym interfejsie nie ustalaj wyniku na podstawie metryk ani nie sprowadzaj serii do jednej wartości.

Analiza zawiera metrics (obiekt przekazywany bez zmian, z ułamkami pokrycia i liczbami znaczników wyznaczonymi przez algorytm), opcjonalne markerPoints oraz artifactNames. Każdy zestaw współrzędnych znaczników jest wyrażony w przestrzeni pikseli własnego skanu — aby umieścić je w jednym układzie, zastosuj metrics.transformation_matrix do punktów zapytania. Mapy bitowe wyników są treścią chronioną: pobieraj je przez punkt końcowy artefaktów, który ponownie autoryzuje każde żądanie i zwraca bajty nienadające się do buforowania.

Etykieta (nazwa w protokole: composite tag, przestrzeń nazw /api/v1/composite-tags) to jedna fizyczna etykieta zawierająca jeden lub więcej identyfikatorów dowolnego typu; DUST nie jest wymagany. Etykiety zajmują pozycje na rolce (collection.kind = "reel", identyfikowanej przez UUID; jej name to wydrukowany numer rolki lub dowolny tytuł i nigdy nie jest unikatowe). Rolki można umieszczać w kolekcji etykiet (kind = "reel_collection"), czyli folderze, który nigdy nie jest wysyłany. Pole expectedIdentifiers rolki określa, ile identyfikatorów każdego typu ma kompletna etykieta na tej rolce, w postaci [{ "tagType", "count" }] (domyślnie po jednym TEXT, DUST i QR; wartość count równa 0 na wejściu oznacza, że dany typ nie jest oczekiwany). Jest to wskazówka dla stanowisk rejestracji, a nie ograniczenie, a pole complete etykiety oznacza, że ma ona co najmniej tyle aktywnych identyfikatorów każdego oczekiwanego typu.

OperacjaMetoda i ścieżka
Lista / tworzenie kolekcji etykietGET, POST /api/v1/composite-tags/collections; PATCH …/collections/{collection_id}
Lista rolekGET /api/v1/composite-tags/reels?collectionId=…&unfiled=…&transferred=any|only|hide&q=…
Tworzenie rolkiPOST /api/v1/composite-tags/reels — { name, description?, collectionId?, expectedIdentifiers? }
Pobieranie / aktualizacja rolkiGET, PATCH /api/v1/composite-tags/reels/{reel_collection_id} (zmiana nazwy, oczekiwany skład, collectionId do przeniesienia; null usuwa ją z kolekcji)
Tworzenie etykietyPOST /api/v1/composite-tags/reels/{reel_collection_id}/labels (multipart)
Dodawanie / usuwanie identyfikatora członkowskiegoPOST /api/v1/composite-tags/{composite_tag_id}/identifiers (multipart); DELETE …/identifiers/{tag_id}
Lista / pobieranie etykietGET /api/v1/composite-tags?reelCollectionId=…&bound=any|only|unbound&transferred=…&q=…; GET …/{composite_tag_id}
Rozpoznawanie etykiety po wartości identyfikatora członkowskiegoPOST /api/v1/composite-tags/resolve — { tagType, value, reelCollectionId? } (TEXT jest dopasowywany bez rozróżniania wielkości liter); odpowiedź zawiera detail (pierwsze dopasowanie) i candidates[] (wszystkie dopasowania, w kolejności pozycji, gdy podano rolkę)
Przenoszenie lub odcinanie etykietPOST /api/v1/composite-tags/move; kontrola wstępna przez POST /api/v1/composite-tags/move/preview
Powiązanie / odłączenie etykietyPOST /api/v1/composite-tags/{composite_tag_id}/bind — { threadId, options?: { indexing: "default" } }; POST …/unbind
Powiązanie zbiorcze zakresu rolkiPOST /api/v1/composite-tags/reels/{reel_collection_id}/bulk-bind — { fromPosition, toPosition, threadIds, activate?, dryRun? }
Archiwizacja / przywracanie etykietyPOST …/{composite_tag_id}/archive, POST …/unarchive
AktywacjaPOST …/{composite_tag_id}/activate; POST /api/v1/composite-tags/reels/{reel_collection_id}/activate (w tle)
Aktywacja samodzielnych identyfikatorów DUSTPOST /api/v1/tags/activate — { tagIds[] } (do 200); jeden wynik na identyfikator, bez możliwości cofnięcia
Okno terminala
curl -fsS "$APID_URL/api/v1/composite-tags/reels" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H "Dust-Ctx-Team-Id: $DUST_TEAM_ID" \
-H "Content-Type: application/json" \
--data '{ "name": "0030", "expectedIdentifiers": [{ "tagType": "TEXT", "count": 1 }, { "tagType": "DUST", "count": 1 }, { "tagType": "QR", "count": 1 }] }'

Odpowiedź to { reel }, podsumowanie rolki z zerowymi licznikami. Zachowaj reel.collectionId; identyfikuje ono rolkę. Utworzenie rolki zawsze tworzy nową rolkę: nie ma ponownego użycia po nazwie.

Każda etykieta to jedno żądanie multipart. Jako data podaj co najwyżej jeden obraz DUST; każdy inny identyfikator członkowski przekazuje się przez identifiers — tablicę JSON z wpisami { tagType, value } lub { tagType: "DUST", fingerprintId } dla kolejnego DUST już wyodrębnionego przez POST /api/v1/tags/extract. humanReadable i qrValue to skróty dla identyfikatorów członkowskich typu TEXT i QR. position domyślnie przyjmuje następną wolną pozycję na rolce.

Okno terminala
curl -fsS "$APID_URL/api/v1/composite-tags/reels/$REEL_COLLECTION_ID/labels" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H "Dust-Ctx-Team-Id: $DUST_TEAM_ID" \
-F "position=1" \
-F "data=@scan.jpeg" \
-F 'identifiers=[{"tagType":"TEXT","value":"AB00001"},{"tagType":"QR","value":"https://v.example/ab00001"}]' \
-F 'options={"indexing":"none"}'

W wyniku musi powstać co najmniej jeden identyfikator. Udana odpowiedź ma outcome: "created"; ponowienie z tym samym oznakowaniem DUST na tej samej pozycji jest uzgadniane jako outcome: "already_enrolled". DUST, który jest już na innej etykiecie lub jest już powiązany, zwraca 409 COMPOSITE_TAG_CONFLICT. Powtórzone wartości TEXT lub QR nigdy nie są odrzucane: zamiast tego rolka zgłasza je w warnings, ponieważ na niektórych rolkach powtarzanie wartości jest zamierzone.

options.indexing wybiera tryb indeksowania DUST dla obrazu w data: default (identyfikowalny), chyba że zażądasz none (tylko weryfikacja). Bezobsługowe stanowiska rejestracji zwykle rejestrują w trybie tylko weryfikacji i aktywują później; aktywacja indeksuje każdy DUST i uruchamia sprawdzanie duplikatów przez platformę, więc etykieta, której DUST powiela już zaindeksowany, zostaje zgłoszona i pominięta.

Ten sam przepływ w kliencie typowanym:

const created = await client.compositeTags.createReel({ name: "0030" });
const form = new FormData();
form.set("position", "1");
form.set("data", scanBlob);
form.set("identifiers", JSON.stringify([{ tagType: "TEXT", value: "AB00001" }]));
await client.compositeTags.createLabel(created.reel.collectionId, form);
const state = await client.compositeTags.getReel(created.reel.collectionId);
await client.compositeTags.activateReel(created.reel.collectionId);

POST /api/v1/composite-tags/move przyjmuje source, target i opcjonalne expectedCount.

Trzy postacie źródła:

  • { compositeTagIds } — ręcznie wybrane etykiety, przenoszone w podanej kolejności.
  • { reelCollectionId, fromPosition, toPosition? } — wpisany zakres pozycji (odcięcie); toPosition domyślnie przyjmuje ostatnią pozycję rolki.
  • { fromCompositeTagId, toCompositeTagId } — odcięcie wyznaczone skanami: pierwsza i ostatnia etykieta odcinka, w dowolnej kolejności. Serwer odczytuje ich pozycje pod blokadą; obie muszą być aktywnymi etykietami na tej samej rolce (w przeciwnym razie endpoints_on_different_reels, endpoint_archived lub endpoint_not_on_reel w detail.reason). Każdą etykietę rozpoznasz na podstawie zeskanowanej wartości przez /resolve (w zakresie rolki, tak aby powtórzony wydrukowany kod pojawił się jako kilka elementów candidates do rozstrzygnięcia przez wywołującego) albo — w przypadku aktywowanego DUST — przez POST /api/v1/tags/identify.

target ma postać { reelCollectionId } lub { newReel: { name, description?, collectionId?, expectedIdentifiers? } } (nowa rolka dziedziczy skład rolki źródłowej, jeśli go nie podano).

Przenoszona jest każda aktywna etykieta w obrębie odcinka; pozycje z zarchiwizowaną lub wysłaną etykietą albo bez etykiety to luki, które pozostają na rolce źródłowej. Pozycje są zachowywane, gdy wszystkie są wolne na rolce docelowej; w przeciwnym razie cała partia jest dopisywana za ostatnią pozycją rolki docelowej w kolejności źródłowej. Odpowiedź zawiera moved[] oraz — dla źródła w postaci zakresu lub odcięcia wyznaczonego skanami — cut: { sourceReel, fromPosition, toPosition, count, boundCount, boundPositions, gaps[] }.

expectedCount sprawia, że liczba staje się warunkiem: jeśli ją podano, przeniesienie zostaje odrzucone z kodem 400 INVALID_REQUEST i detail.reason: "count_mismatch" (expected, actual, fromPosition, toPosition), chyba że przeniesionych zostałoby dokładnie tyle aktywnych etykiet.

POST /api/v1/composite-tags/move/preview przyjmuje to samo source, opcjonalne target i expectedCount, niczego nie zmienia i zwraca span, count, boundCount, etykiety first i last partii, predictedOutcome (kept_positions / appended / null), countMatches oraz suggestedLast — etykietę położoną dalej na rolce, przy której zostałby spełniony warunek expectedCount, gdy odcinek jest za krótki. To tylko sugestia, którą operator może zeskanować; serwer nigdy jej nie stosuje. Podgląd jest dostępny na poziomie członka; przeniesienie wymaga administratora zespołu lub organizacji.

POST /api/v1/composite-tags/reels/{reel_collection_id}/bulk-bind wiąże etykiety na pozycjach fromPosition..toPosition (włącznie) z threadIds po kolei: k-tą pozycję z k-tym rekordem. Operacja jest typu „wszystko albo nic” i ścisła: zakres musi obejmować dokładnie threadIds.length pozycji (toPosition jest wymagane, a nie wyliczane, aby wywołujący wskazał zakres sprawdzony na rolce), w jednym wywołaniu może być najwyżej 500 par, a każda pozycja musi zawierać aktywną, niepowiązaną etykietę. Serwer nigdy nie pomija pozycji, ponieważ pominięcie po cichu przesunęłoby każde kolejne przyporządkowanie.

Przekaż dryRun: true, aby wykonać kontrolę wstępną bez wiązania. Postać odpowiedzi jest w obu przypadkach taka sama:

  • outcome — "bound" po rzeczywistym powiązaniu, "preflight" dla przebiegu próbnego.
  • rows[] — jeden wiersz na każde przyporządkowanie: index, position, compositeTagId (null dla pustej pozycji), labelName, textValue (identyfikator członkowski TEXT etykiety, czyli wydrukowany kod), threadId, threadName, threadDescription.
  • blockers[] i warnings[] — { kind, index, position, compositeTagId?, threadId?, tagType?, existing? }.
  • activation — "queued", "not_requested", "already_active" lub "no_dust".
  • reel — podsumowanie rolki ze zaktualizowanymi licznikami.

Rodzaje blokad: position_empty, label_archived, label_transferred, label_bound, label_no_identifiers, identifier_bound_elsewhere, identifier_in_other_team_label, thread_not_owned, thread_unavailable, thread_in_transfer, thread_not_editable, thread_repeated. Rodzaje ostrzeżeń: label_incomplete (etykieta z mniejszą liczbą identyfikatorów, niż oczekuje rolka) i thread_has_label (rekord ma już etykietę; wskazuje je existing[]). Ostrzeżenia nigdy nie zatrzymują powiązania.

Zatwierdzenie z jakąkolwiek blokadą kończy się niepowodzeniem z kodem 409 COMPOSITE_TAG_CONFLICT; detail zawiera te same rows, blockers i warnings co przebieg próbny, więc klient zawsze analizuje tylko jedną postać. Zakres, którego długość różni się od threadIds.length, daje 400 INVALID_REQUEST.

Uprawnienia: członkostwo w zespole rolki oraz uprawnienie do edycji każdego rekordu. Rekord, którego wywołujący nie może edytować, jest blokadą thread_not_editable dla danego wiersza, a nie powodem odrzucenia całego żądania, a każdy rekord musi należeć do zespołu rolki — rekord jedynie udostępniony zespołowi daje thread_not_owned.

Przy activate: true powiązanie zostaje najpierw zatwierdzone, a następnie jedno zadanie w tle aktywuje oznakowania DUST dokładnie tych powiązanych etykiet; etykieta, której aktywacja nie powiedzie się, pozostaje powiązana w trybie tylko weryfikacji. Postęp sprawdzaj, odpytując counts.identifiableCount rolki.

Okno terminala
# Preflight
curl -fsS "$APID_URL/api/v1/composite-tags/reels/$REEL_COLLECTION_ID/bulk-bind" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H "Dust-Ctx-Team-Id: $DUST_TEAM_ID" \
-H "Content-Type: application/json" \
--data '{ "fromPosition": 1, "toPosition": 3, "threadIds": ["'$THREAD_1'", "'$THREAD_2'", "'$THREAD_3'"], "dryRun": true }'
# Commit, activating the bound Labels afterwards
curl -fsS "$APID_URL/api/v1/composite-tags/reels/$REEL_COLLECTION_ID/bulk-bind" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H "Dust-Ctx-Team-Id: $DUST_TEAM_ID" \
-H "Content-Type: application/json" \
--data '{ "fromPosition": 1, "toPosition": 3, "threadIds": ["'$THREAD_1'", "'$THREAD_2'", "'$THREAD_3'"], "activate": true }'

Z klientem typowanym:

const preview = await client.compositeTags.bulkBind(reelCollectionId, {
fromPosition: 1,
toPosition: threadIds.length,
threadIds,
dryRun: true,
});
if (preview.blockers.length === 0) {
await client.compositeTags.bulkBind(reelCollectionId, {
fromPosition: 1,
toPosition: threadIds.length,
threadIds,
activate: true,
});
}

Powiązanie pozostawia dokładnie te same zdarzenia co N pojedynczych powiązań: jedno zdarzenie bind na każdy identyfikator członkowski, z rekordem, identyfikatorem i etykietą jako obiektami docelowymi, wszystkie ze wspólnym ID operacji.

Etykieta jest wiązana w całości: POST …/{composite_tag_id}/bind wiąże etykietę i każdy aktywny identyfikator członkowski z rekordem; options.indexing: "default" dodatkowo ją aktywuje. To samo dzieje się, gdy wywołasz zwykłe POST /api/v1/tags/bind z dowolnym identyfikatorem członkowskim (patrz Powiązanie etykiety) — tak właśnie działają skanery. Powiązanie wymaga uprawnienia do edycji rekordu, etykieta musi należeć do zespołu, a rekord — do tego samego zespołu: identyfikator członkowski zeskanowany na rekordzie, który udostępnił ci inny zespół, zostaje odrzucony (409 COMPOSITE_TAG_CONFLICT, reason: "label_owned_by_other_team"), a nie powiązany jako samodzielna kopia. POST …/unbind odłącza etykietę i wszystkie jej identyfikatory członkowskie.

Własność zespołu i organizacji wynika wyłącznie z nagłówków kontekstu, a twórca — ze zweryfikowanego tokenu Bearer. Odczyt i aktywacja etykiet wymagają członkostwa w zespole. Tworzenie rolek, rejestracja, przenoszenie i archiwizacja są dostępne dla zalogowanego administratora zespołu lub organizacji albo dla konta usługi o zakresie organizacji, które jest członkiem wybranego zespołu; konto usługi nie może przejąć uprawnień administratora organizacji.

W przesyłkach rolka jest pozycją manifestu ({ kind: "reel", collectionId }) i jest wysyłana w całości, ale tylko wtedy, gdy każda etykieta na niej jest niepowiązana. Powiązana etykieta jest wysyłana razem ze swoim rekordem i nigdy nie dołącza swojej rolki do przesyłki. Wysłane rolki i etykiety pozostają czytelne po stronie nadawcy z ustawionym transferredAt; możesz je filtrować za pomocą transferred=only lub transferred=hide.

Opis przepływu pracy w DICE znajdziesz w artykule Etykiety i rolki.