SeaTable Permissions per API

Pattern fuer kontrollierten Mitarbeiter-Zugriff auf eine SeaTable-Base ohne UI-Geklicke. Geschrieben am Erlei-Auftrag (Mai 2026), nutzbar als Vorlage fuer kuenftige Kunden mit aehnlichem Setup.

Was geht und was nicht

AufgabeAPI verfuegbarAnmerkung
Schema lesen (Tabellen, Spalten, Views)via Base-Token + JWT
View erstellen / aendern (Filter, Hidden Cols, Sortierung)via Base-Token + JWT
User per Email finden ( @auth.local-ID)Account-Token, /api2/search-user/
User-View-Share setzen (User bekommt nur diese View)Account-Token
User-View-Share Permission updaten (r ↔ rw)Account-Token, PUT .../user-view-shares/<share_id>/ — keine DELETE+POST noetig
Group-View-Share setzenAccount-Token
Primary Column / Display Column einer Tabelle aendern✗/WorkaroundErste Spalte ist hart gepinnt. Workaround: erste Spalte auf Formula-Typ umstellen, Concat-Formel reinpacken (siehe Sektion „Display-Column im Link-Picker”).
Inline neue verlinkte Datensaetze aus einem Form anlegenForms erlauben nur Auswahl bestehender Records. Workarounds in Sektion „Forms-Limitations”.
Spalten-Permissions per Gruppe (Hidden fuer Gruppe X)Nicht in API. Loesung: Hidden Cols in der View, View teilen.
User in Workspace einladen(offen)im Erlei-Fall war MA schon im Workspace, daher nicht getestet. Vermutlich /api/v2.1/... mit Account-Token.
Statistics-Pivot-Sichtbarkeit pro User(offen)Hypothese: User-View-Share macht Base zur Whitelist, dann sind Statistics auch ausgeblendet. Beim Test verifizieren.

Modell verstehen — User-View-Share als Whitelist

SeaTable hat zwei Permission-Mechaniken:

  1. Base-Sharing (POST .../user-shares/) — User wird Editor/Viewer der gesamten Base.
  2. User-View-Share (POST .../user-view-shares/) — User sieht nur die explizit geteilten Views.

Fuer kontrollierten MA-Zugriff ist Variante 2 die richtige. Die View enthaelt:

  • Filter = welche Zeilen
  • Hidden Columns = welche Spalten
  • Permission r oder rw = lesen vs schreiben

Damit ist alles in der View, keine separate “Spalten-Permission-Schicht” noetig.

Token-Typen — wichtig zu unterscheiden

SeaTable hat drei Token-Arten. Jeder funktioniert nur fuer bestimmte Endpoints — Verwechslung gibt 401/404.

Token-TypWo erstellenKannHeader
Base API TokenBase oeffnen ”…”-Menue API TokenDaten lesen/schreiben einer BaseAuthorization: Token <token>
App Access Token (JWT)abgeleitet aus Base-Token via POST /api/v2.1/dtable/app-access-token/Schema/View-Operationen ueber api-gatewayAuthorization: Bearer <jwt>
Account API TokenAvatar oben rechts Account Settings API TokensUser-Lookup, Sharing, Workspace-OperationenAuthorization: Token <token>

Goldene Regel: Wenn ein Endpoint /api/v2.1/workspace/... enthaelt Account-Token. Wenn /api-gateway/api/v2/dtables/... JWT. Wenn /api/v2.1/dtable/... (Singular) Base-Token.

Wichtige Endpoints

App-Access-Token aus Base-Token holen

JWT=$(curl -sS 'https://cloud.seatable.io/api/v2.1/dtable/app-access-token/' \
  -H "Authorization: Token <BASE_TOKEN>" \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')

Antwort enthaelt zusaetzlich dtable_uuid, workspace_id, dtable_name, dtable_server. Die UUID ist persistent — einmal merken.

Schema laden (mit JWT)

curl -sS "https://cloud.seatable.io/api-gateway/api/v2/dtables/<UUID>/metadata/" \
  -H "Authorization: Bearer ${JWT}"

Liefert komplettes JSON mit allen Tabellen, deren Spalten (mit key, name, type) und Views (mit _id, name, filters, hidden_columns, groupbys, private_for).

View aendern (Filter + Hidden Cols)

VIEW=$(python3 -c "import urllib.parse; print(urllib.parse.quote('Alle Auftraege (offen)'))")
TABLE=$(python3 -c "import urllib.parse; print(urllib.parse.quote('Auftraege'))")
curl -sS -X PUT "https://cloud.seatable.io/api-gateway/api/v2/dtables/<UUID>/views/${VIEW}/?table_name=${TABLE}" \
  -H "Authorization: Bearer ${JWT}" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [{"column_key": "<col-key>", "filter_predicate": "is", "filter_term": "<option-id>"}],
    "filter_conjunction": "And",
    "hidden_columns": ["<key1>", "<key2>"]
  }'

Spalten-Keys und Single-Select-Option-IDs aus der Metadata raussuchen. View-Namen mit Umlauten URL-encoden.

User per Email suchen (Account-Token)

curl -sS "https://cloud.seatable.io/api2/search-user/?q=<email-or-substring>" \
  -H "Authorization: Token <ACCOUNT_TOKEN>"

Antwort: {"users": [{"email": "<id>@auth.local", "contact_email": "<echte-mail>", "name": "..."}]}. Das email-Feld ist die interne ID, die fuer Sharing-Calls als to_user gebraucht wird. Substring-Match — auf contact_email exakt vergleichen, sonst Verwechslung.

User-View-Share anlegen (Account-Token)

curl -sS -X POST "https://cloud.seatable.io/api/v2.1/workspace/<WS_ID>/dtable/<BASE_NAME>/user-view-shares/" \
  -H "Authorization: Token <ACCOUNT_TOKEN>" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "permission=rw&to_user=<id>@auth.local&table_id=<table-id>&view_id=<view-id>"

Form-Encoded, nicht JSON. permission ist r oder rw. Owner kann sich selbst nicht sharen — gibt 400 "View None cannot be shared with its owner.".

Bestehende Shares listen

curl -sS "https://cloud.seatable.io/api/v2.1/workspace/<WS_ID>/dtable/<BASE_NAME>/user-view-shares/" \
  -H "Authorization: Token <ACCOUNT_TOKEN>"

Idempotenz-Check vor neuem Anlegen. Listing-Response enthaelt id pro Share — die wird fuer Updates und Deletes gebraucht.

Permission updaten (Account-Token)

curl -sS -X PUT "https://cloud.seatable.io/api/v2.1/workspace/<WS_ID>/dtable/<BASE_NAME>/user-view-shares/<SHARE_ID>/" \
  -H "Authorization: Token <ACCOUNT_TOKEN>" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "permission=rw"

Body Form-Encoded mit nur permission. Doku: api.seatable.com/reference/updateuserviewshare. Kein DELETE+POST noetig — sauber per PUT. Beispiel-Implementierung als update_share_permission() in ../../runs/2026-04-20-erlei-seatable-scan/scripts/aktiviere_ma.py (eingebaut 2026-05-07 fuer Markus’ rw-Korrektur).

shared_name pro Share setzen (kosmetisches Tile-Label)

curl -sS -X PUT "https://cloud.seatable.io/api/v2.1/workspace/<WS_ID>/dtable/<BASE_NAME>/user-view-shares/<SHARE_ID>/" \
  -H "Authorization: Token <ACCOUNT_TOKEN>" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "permission=rw" \
  --data-urlencode "shared_name=Aufträge offen"

Wichtig — permission muss mitgesendet werden, auch wenn nur shared_name geaendert werden soll. Ohne permission antwortet die API mit 400 "permission invalid". shared_name UTF-8 (Em-Dashes/Umlaute OK), bis ~80 Zeichen visuell sinnvoll. Setzt das Label im „An mich freigegebene Bases”-Tile des Empfaengers.

Use Case: wenn dem User-View-Share-Empfaenger 5+ Sichten der gleichen Base freigegeben sind, sieht er ohne shared_name mehrere Tiles mit identischem Base-Namen — dann ist das Label-Setting Pflicht zur Unterscheidung. Pattern aus Erlei-Run 2026-05-20.

Stolperer

  • load_view_index braucht JWT — der Account-Token greift auf das Schema nicht durch. Das Aktivierungs-Skript loest das ueber eine vorab gespeicherte metadata.json-Datei (Vorbereitungs-Schritt).
  • View-Namen mit Umlauten zwingend URL-encoden, sonst 404.
  • Hidden-Cols-Liste komplett ersetzen — die API merged nicht, sondern uebernimmt die Liste 1:1. Beim Update vorhandene Hidden Cols mitschicken.
  • Filter-Term ist die Option-ID, nicht der Label-Name bei Single-Select. Aus column.data.options[].id raussuchen.
  • Owner-Token kann sich selbst keine Shares geben — Test mit Owner-Email gibt erwartete 400er.
  • Statistics-Pivots sind nicht Teil der View-Liste — separate Sichtbarkeitsregeln (vermutlich „nur Owner” wenn User-View-Share aktiv). Beim ersten echten Test mit Mitarbeiter-Account verifizieren.
  • forms-Endpoint im api-gateway antwortet nicht wie erwartet — Pull via /api-gateway/api/v2/dtables/<uuid>/forms/ liefert kein nutzbares JSON. Forms sind aber von Row-Operationen nicht betroffen (sie haengen am table_id, nicht an Row-IDs), Schema-Integritaet kann via /metadata/ verifiziert werden.

Test-Daten loeschen — Reihenfolge bei verlinkten Tabellen

Wenn ein Kunde eine Test-Phase hatte und vor Live-Betrieb komplett bereinigt werden soll, ist die Lösch-Reihenfolge entscheidend, sonst gibt’s orphaned Links (verwaiste Verlinkungen) oder Schema-Fehler.

Goldene Regel: zuerst Kind-Tabellen, dann Eltern-Tabellen, dann Stammdaten. Konkret:

  1. Position-/Detail-Tabellen (Positionen, Fremdkosten, Zeiterfassung — alles was per Link auf einen Auftrag zeigt)
  2. Auftrags-/Vorgangs-Tabelle (Aufträge, Tickets, Projekte — die Eltern-Entitaet)
  3. Stammdaten (Auftraggeber, Versicherungsnehmer, Besichtigungsorte, Fremddienstleister — referenziert FROM Auftraegen)

Warum: SeaTable’s Row-Delete entfernt nur die Zeile, die Verlinkung (Link-Spalte) wird beim Pull der noch existierenden Eltern-Zeile leer. Wenn du die Eltern-Tabelle zuerst leerst, sind die Kind-Zeilen orphaned aber funktional intakt — Link-Spalten zeigen auf nichts. Saubere Reihenfolge vermeidet das.

API-Mechanik:

# JWT holen wie oben, dann pro Tabelle:
 
# 1. Alle Row-IDs holen
curl -sS "https://cloud.seatable.io/api-gateway/api/v2/dtables/<UUID>/rows/?table_id=<TABLE_ID>&limit=1000" \
  -H "Authorization: Bearer ${JWT}" | jq -r '.rows[]._id'
 
# 2. Batch-Delete (max 1000 pro Request)
curl -sS -X DELETE "https://cloud.seatable.io/api-gateway/api/v2/dtables/<UUID>/rows/" \
  -H "Authorization: Bearer ${JWT}" \
  -H "Content-Type: application/json" \
  -d '{"table_id": "<TABLE_ID>", "row_ids": ["row1", "row2", ...]}'

Skript-Vorlage: ../../runs/2026-04-20-erlei-seatable-scan/scripts/loesche_test_daten.py — implementiert Dry-Run-Modus (zeigt Bestand vor Lauf), automatische Reihenfolge, Verify-Pull nach Lauf. Funktional getestet im Erlei-Run am 2026-05-04 abend (23 Zeilen aus 7 Tabellen, alle leer).

Was Row-Delete NICHT betrifft (bleibt nach Bereinigung intakt):

  • Schema (Tabellen, Spalten, Spalten-Typen, Link-Definitionen)
  • Views (Filter, Hidden Cols, Sortierung, Groupbys)
  • user-view-shares (Permissions haengen am view_id)
  • Forms (haengen am table_id, nicht an Rows)
  • Statistics-Pivots (Konfiguration unabhaengig von Daten)
  • Link-Formula-Spalten (Summe Stunden etc. — laufen wieder live sobald echte Daten reinkommen)

Multi-User-Risiko: rw-User koennte Filter modifizieren

Problem: Der user-view-share-Mechanismus mit permission=rw erlaubt dem User Schreib-Zugriff auf die Sicht. Was nicht eindeutig dokumentiert ist: kann ein rw-User den Filter der geteilten Sicht aendern? Wenn ja, dann ist eine Zeilen-basierte Sichtbarkeit (z.B. „Mitarbeiter sieht nur eigene Stunden via Bearbeiter include_me”) nicht hart geschuetzt — der User koennte den Filter wegnehmen und alle Zeilen sehen.

Recherche-Stand 2026-05-04:

  • SeaTable custom sharing docs — bestaetigt dass andere Tabellen versteckt bleiben, aber kein Statement zu Filter-Modifikation
  • Forum-Thread — diskutiert die Mechanik aber liefert keine harte Antwort

Live-Test-Aufgabe (offen, Trigger: erste Mitarbeiter-Registrierung): Mitarbeiter-Account einloggen, in die rw-Sicht gehen, versuchen den Filter abzuschalten oder zu modifizieren. Wenn das geht: rw ist unsicher fuer Zeilen-Schutz.

Mail-Wording-Pattern wenn Sicherheit nicht 100% verifiziert ist:

Statt zusicherndem Wording wie „die Stunden des Owners bleiben fuer den Mitarbeiter unsichtbar” — beschreibendes Wording verwenden: „die Sicht ist so eingestellt, dass sie gefiltert auf seinen Bearbeiter nur seine eigenen Stunden zeigt”. Plus einen Joint-Quickcheck beim ersten Login ankuendigen, bei dem der Berater zusammen mit dem Mitarbeiter die Sicht-Konfiguration einmal verifiziert.

Das schuetzt:

  • Den Berater (keine zusicherbaren Aussagen die spaeter zerbrechen koennen)
  • Den Kunden (gemeinsamer Quickcheck deckt unentdeckte Schwachstellen sofort auf)
  • Die Architektur-Optionalitaet (falls Filter-Modifikation moeglich ist: Architektur-Anpassung von rw → r + separates Eingabe-Form, ohne dass eine peinliche Korrektur-Mail noetig ist)

Architektur-Fallback falls Live-Test ergibt dass Filter modifizierbar ist:

  • rw-Sicht zur Datenanzeige → r-Sicht umbauen (Lesen ja, Schreiben nicht)
  • Eingabe der Daten ueber ein separates Form das nur per POST /api-gateway/.../rows/ schreibt und implizit Bearbeiter = currentUser setzt
  • Mitarbeiter sieht nur eigene Daten (gefiltert), schreibt nur eigene Daten (forciert), Filter-Modifikation bringt nichts mehr

Befund: SeaTable-Link-Felder zeigen im Picker (in Forms wie auch in Tabellen-Sicht) immer die erste Spalte (primary column) der Ziel-Tabelle. Es gibt keine Picker-eigene „Display-Column”-Konfiguration und keinen API-Call der die Primary Column verschiebt.

Statement im SeaTable-Forum (Karlheinz, offiziell): „The first column is used to identify the row, so it cannot be replaced by other columns in the table.”Forum-Thread.

Workaround (offiziell unterstuetzt seit Release 3.5): Backup-Spalte anlegen, Werte rueberkopieren, dann erste Spalte auf Formula-Typ umstellen mit Concat-Formel.

# Schritt 1: Backup-Text-Spalte anlegen (POST /columns/)
curl -sS -X POST "https://cloud.seatable.io/api-gateway/api/v2/dtables/<UUID>/columns/" \
  -H "Authorization: Bearer ${JWT}" \
  -H "Content-Type: application/json" \
  -d '{"table_name": "Aufträge", "column_name": "Auftragsnummer (Original)", "column_type": "text"}'
 
# Schritt 2: Werte aus First-Column in Backup-Spalte kopieren via Batch-Update (PUT /rows/)
#   Update-Body: {"table_name": "Aufträge", "updates": [{"row_id": "...", "row": {"Auftragsnummer (Original)": "26-001-ME"}}, ...]}
 
# Schritt 3: First-Column auf Formula umstellen (PUT /columns/)
curl -sS -X PUT "https://cloud.seatable.io/api-gateway/api/v2/dtables/<UUID>/columns/" \
  -H "Authorization: Bearer ${JWT}" \
  -H "Content-Type: application/json" \
  -d '{
    "table_name": "Aufträge",
    "op_type": "modify_column_type",
    "column": "Auftragsnummer",
    "new_column_type": "formula",
    "column_data": {"formula": "{Auftragsnummer (Original)} & \" — \" & {Warenart}"}
  }'

API-Konventionen die nicht in der offiziellen Doku stehen (Erlei-Run 2026-05-20):

  • PUT /columns/ braucht zwingend op_type — sonst HTTP 400 „op_type invalid”. Gueltige Werte u.a.: rename_column, modify_column_type, resize_column, move_column, freeze_column.
  • Formel kommt in column_data, nicht new_column_data und nicht data — typischer Fallstrick: API-Aufruf gibt HTTP 200 zurueck (Typ wird umgestellt), aber die Formel landet nicht. Verify-Pull zeigt dann data: {"default_value":"","enable_fill_default_value":false}.
  • Pflicht-Reihenfolge bei produktiven Daten: Backup zuerst, Typ-Wechsel zuletzt. Beim Typ-Wechsel verschwinden die Werte der First-Column unwiederbringlich — wenn die Backup-Spalte vorher nicht gesetzt ist, sind die Rohdaten weg.
  • Formel referenziert Spaltennamen, nicht Keys{Spaltenname} mit echtem Display-Namen, Umlaute OK.
  • Concat-Operator ist &, nicht + oder concat(). Beispiel: {ColA} & " — " & {ColB}.

Stolperer:

  1. Inhalt der ersten Spalte verschwindet beim Typ-Wechsel — Formula-Spalte berechnet sich aus den Inputs, alte Werte sind weg. Vor dem Lauf eine Backup-Spalte anlegen (Text-Spalte mit Inhalt der ersten Spalte rueberkopieren), oder Tabelle vor dem Lauf duplizieren.
  2. Wirkt global — die neue Display erscheint ueberall wo die Tabelle verlinkt ist (alle Forms, alle Sichten der Eltern-Tabellen, alle Statistics-Pivots). Vor dem Lauf mit dem Kunden bestaetigen, dass die globale Wirkung erwuenscht ist.
  3. Bestehende Verlinkungen bleiben funktional intakt — Links referenzieren _id, nicht den Anzeigewert. Aber alle Stellen wo der Anzeige-String benutzt wurde (z.B. Mail-Templates, Exports, Lookup-Felder die auf die first column zeigen) muessen mitgedacht werden.
  4. Ab Release 3.5+ sind Formulas in der ersten Spalte explizit unterstuetzt. In aelteren Versionen ggf. nicht — Server-Version pruefen.
  5. ⚠ Formula-First-Column bricht Web-Forms (Erlei-Run 2026-05-21): Wenn die Ziel-Tabelle ein Web-Form hat in dem die First-Column als Eingabe-Feld vorkommt, rendert SeaTable das Feld nach dem Typ-Wechsel ohne Input (Label sichtbar, aber kein Textfeld). Markus konnte nach dem Block-C-Lauf keine neuen Auftraege mehr anlegen weil das Form „Auftragserfassung” die First-Column als read-only Formula-Spalte anzeigte. Rollback nach 24h. Pre-Check vor First-Column-Mutation: Forms-Liste der Tabelle pruefen — wenn ein Form existiert in dem die First-Column ein Eingabe-Feld ist, NICHT mutieren, stattdessen Variante mit display_column_key in den Link-Spalten der referenzierenden Tabellen waehlen (siehe naechster Block).
  6. Form-Editing-API ist seit v5.3 nicht oeffentlich: weder /dtable-server/api/v1/dtables/<uuid>/forms/ (deprecated, redirect zu API Gateway) noch /api-gateway/api/v2/dtables/<uuid>/forms/ (404) noch /api/v2.1/workspace/<ws>/dtables/<uuid>/forms/ (404) liefern Form-Metadata. Form-Anpassungen (Feld hinzufuegen/entfernen, Label-Override) gehen nur ueber die SeaTable-UI. Konsequenz: Wenn ein Form ein Feld braucht das umbenannt/ersetzt werden muss, ist das ein UI-Handgriff fuer den Kunden oder Marvin, nicht via API loesbar.

Seit irgendeinem Release (Stand 2026-05-21 in Cloud aktiv) hat jede Link-Spalte ein Feld display_column_key in ihrem data-Objekt. Default ist "0000" (First-Column der Ziel-Tabelle). Das laesst sich offenbar pro Link-Spalte aendern — heisst der Picker zeigt dann nicht die First-Column sondern eine beliebige andere Spalte als Anzeige-String. Damit wird der Forum-Statement von Karlheinz (Zeile 227 oben) zumindest fuer den Picker-Kontext relativiert — die First-Column bleibt das Row-Identifier-Konzept, aber die Anzeige im Picker ist konfigurierbar.

Saubere Variante (statt First-Column zu Formula machen):

  1. Eine zusaetzliche Formula-Spalte in der Ziel-Tabelle anlegen, z.B. Picker-Anzeige = {Auftragsnummer} & ", " & {Warenart}. First-Column bleibt unangetastet, Forms bleiben funktional.
  2. In jeder referenzierenden Link-Spalte das display_column_key von 0000 auf den Key der neuen Formula-Spalte aendern.

API-Pre-Check vor produktivem Einsatz: ob display_column_key ueberhaupt via PUT /columns/ mit op_type=modify_column_metadata (oder aehnlich) modifizierbar ist, war zum 2026-05-21 noch nicht in einem Test-Base verifiziert. Erst in Test-Base klaeren, dann produktiv.

Doku: first-column-features, Release-3.5-Notes.

Forms-Limitations — kein Inline-Create fuer verlinkte Datensaetze

Befund: SeaTable Web-Forms erlauben im Link-Picker keine Anlage neuer Datensaetze. Nur Auswahl bestehender Records. Gilt seit SeaTable 2.0, ist offener Feature-Request.

In der Tabellen-View funktioniert Inline-Create (im Link-Picker-Dropdown gibt’s „neue Zeile erstellen”) — nur Forms haben diese Einschraenkung. Forum-Threads: allow-link-other-record-column-in-forms, creating-new-linked-records-from-searching-existing-records-doesnt-work.

Drei Workarounds, in Reihenfolge der Pragmatik:

#WorkaroundAufwandTrade-off
AForm weglassen, direkt in der Tabelle erfassen0UX-Umstellung. In der Tabellen-View geht Inline-Create im Link-Picker. Funktioniert sofort, andere UX als Form. Default-Empfehlung wenn Form nicht zwingend ist.
BForm mit Plain-Text-Feldern + Automation~3-4hForm sammelt Stammdaten als Text-Felder. Trigger row_added mit add_link-Action und Match-Bedingung. Bei No-Match: parallele Python-Action die per API einen neuen Stammdatensatz anlegt und dann verlinkt. Doku: link-entries-automation, automation-actions.
CCustom-Frontend statt SeaTable-Form~6-8hEigenes Web-Form ausserhalb SeaTable, ruft /rows/-API direkt. Pro Feld erst GET mit Filter, falls leer dann POST in Stammdaten, dann den neuen _id als Link mitschreiben. Sauberste UX, aber Wartung.

Webhook-Variante als Sub-Pattern: SeaTable hat einen Base-Webhook auf Row-Add (https://seatable.com/help/what-are-webhooks-and-how-to-use-them/). Form-Submit ist ein Row-Add und feuert den Webhook. Externes Skript prueft, legt fehlende an, schreibt Link via JWT-API zurueck. Funktional gleich wie B, aber Logik ausserhalb SeaTable — nuetzlich wenn schon n8n/eigener Server vorhanden ist.

Default-Empfehlungs-Reihenfolge fuer Kundengespraech: A vor B vor C. Erst fragen ob die UX-Umstellung okay ist — meistens ja, dann sind null Kosten. Wenn nein, Option B als bezahlte Mini-Beauftragung (~€300-400 Range).

Konkrete Vorlage — Aktivierungs-Skript Erlei

Skript-Pfad: ../../runs/2026-04-20-erlei-seatable-scan/scripts/aktiviere_ma.py

Aufruf:

# Vorbereitung einmalig — Metadata cachen
TOKEN=$(curl -sS 'https://cloud.seatable.io/api/v2.1/dtable/app-access-token/' \
  -H "Authorization: Token <BASE_TOKEN>" \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')
curl -sS "https://cloud.seatable.io/api-gateway/api/v2/dtables/<UUID>/metadata/" \
  -H "Authorization: Bearer $TOKEN" > /tmp/erlei-meta.json
 
# Aktivierung
SEATABLE_ACCOUNT_TOKEN=<account-token> \
  python3 ../../runs/2026-04-20-erlei-seatable-scan/scripts/aktiviere_ma.py <ma-email>

Output: pro geplantem Share entweder + angelegt, = bereits vorhanden, oder ! FEHLER.

Verwandte Quellen

  • _index — Hub aller MCP-Setups (SeaTable noch nicht eigenes MCP, aber Pattern gleich)
  • anthropic-skills — wann claude-api Skill triggert