Basalt docs
GitHub

Automation

Personal access tokens, webhooks, import and export, and link unfurling — the operations where the instance talks to the outside world. Plus the one pair that runs the other way: the early-access signup, which is the only part of this API a caller reaches with no session at all.

23 operations. Every path is relative to the instance origin; every response body is JSON unless stated. See API for authentication, errors and pagination.

POST /api/v1/waitlist

Join an early-access list. Unauthenticated, rate-limited, and outside the /workspaces/:workspaceId scope — the caller has no account yet. Creates an UNCONFIRMED signup and sends one confirmation mail (double opt-in): nothing is written to the address again until the link in it is followed, and an unconfirmed signup is deleted after 14 days. Answers {"status":"pending"} for a new address AND for one already on the list, deliberately — a differing answer would tell a stranger whether an address is known here. Never returns the row or the token.

createWaitlistSignup · token scope: session only

Request bodyapplication/json, required

  • email · string (email) — required, length 0–320
  • locale · "de" | "en" — required
  • product · "basalt" | "lithic" — required

Response 200application/json

  • status · "pending" | "confirmed" | "removed" — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/waitlist/confirm

Redeem a confirmation token and record consent (timestamp plus the confirming client's address, GDPR Art. 7(1)). Unauthenticated and rate-limited. Single use: a second attempt with the same token is refused like an unknown one. Every failure — unknown token, expired token, already confirmed — answers with ONE opaque error, so the route cannot be used to learn whether a token or an address exists.

confirmWaitlistSignup · token scope: session only

Request bodyapplication/json, required

  • token · string — required, length 1–200

Response 200application/json

  • status · "pending" | "confirmed" | "removed" — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/waitlist/unsubscribe

Withdraw consent and leave the list. Unauthenticated and rate-limited, and it takes the SAME token the confirmation mail carried — GDPR Art. 7(3) wants withdrawal to be as easy as consent was, and consent was one click. Unlike confirming, this accepts an EXPIRED token and an already-confirmed one: somebody who kept the mail for a month must still be able to leave. The row is DELETED, IP evidence included — there is no reason to keep proof of a consent that has been withdrawn. Idempotent by construction: a token that matches nothing (because it already unsubscribed) answers {"status":"removed"} too, so a second click cannot report a failure for something that is already true.

unsubscribeWaitlistSignup · token scope: session only

Request bodyapplication/json, required

  • token · string — required, length 1–200

Response 200application/json

  • status · "pending" | "confirmed" | "removed" — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces/{workspaceId}/unfurl

Fetch public metadata (title, description, icon, preview image, whether it may be framed) for a URL, so a pasted link can be shown as a titled link, a bookmark card or an embed. This makes the instance issue an outbound request to the URL, from its own IP address. The address is resolved, refused unless globally routable, and pinned into the connection; redirects are never followed. Answers are cached. Operators can disable the whole route with UNFURL=off, which makes it 403.

unfurlUrl · token scope: content:write

Path parameters

  • workspaceId · string (uuid) — required

Request bodyapplication/json, required

  • url · string (uri) — required, length 0–2000

Response 200application/json

  • url · string — required
  • title · string | null — required
  • description · string | null — required
  • siteName · string | null — required
  • iconUrl · string | null — required
  • imageUrl · string | null — required
  • embeddable · "allowed" | "refused" | "unknown" — required
  • cached · boolean — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

GET /api/v1/workspaces/{workspaceId}/export

Download the workspace as a zip: one Markdown file per page (folder tree mirroring the page hierarchy, field values as YAML frontmatter), one per outline under /outlines/ (inline marks flattened, which a page's body keeps), the attachments those pages reference, and basalt-export.json carrying every page's and outline's verbatim title and path plus the collections, databases, fields, bindings, views and row memberships Markdown cannot express. All of it — structure included — is filtered to what the caller may read; an export is not a way around permissions.

exportWorkspace · token scope: content:read

Path parameters

  • workspaceId · string (uuid) — required

Response 200application/json

  • string

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces/{workspaceId}/import

Import a Notion zip, a Markdown/Obsidian folder or a Basalt export into a collection (multipart/form-data: one or more file parts — a .zip is expanded, loose files keep their relative path — plus either an existing collectionId (requires edit on it) or a collectionName to create one (requires collections.create). Answers a report of what came in and what did not: unresolved links, skipped files, inferred column types and the decisions taken on your behalf. Not transactional and not rolled back on partial failure — every id is derived from the source path, so re-running the same archive into the same collection resumes it instead of duplicating it.

importWorkspace · token scope: content:write

Path parameters

  • workspaceId · string (uuid) — required

Response 200application/json

  • formatVersion · 2 — required
  • runId · string (uuid) — required
  • status · "complete" | "partial" — required
  • source · "basalt" | "notion" | "markdown" | "confluence" — required
  • collectionId · string (uuid) — required
  • collectionName · string — required
  • collectionCreated · boolean — required
  • counts · object — required
    • pages · integer — required, -9007199254740991–9007199254740991
    • databases · integer — required, -9007199254740991–9007199254740991
    • rows · integer — required, -9007199254740991–9007199254740991
    • fieldsCreated · integer — required, -9007199254740991–9007199254740991
    • fieldsReused · integer — required, -9007199254740991–9007199254740991
    • attachments · integer — required, -9007199254740991–9007199254740991
    • linksRewritten · integer — required, -9007199254740991–9007199254740991
    • linksUnresolved · integer — required, -9007199254740991–9007199254740991
  • unresolved · object[] — required
    • from · string — required
    • target · string — required
  • skipped · object[] — required
    • path · string — required
    • reason · "unsupported-file" | "unsupported-image" | "html-export" | "too-large" | "empty" | "duplicate-view-csv" — required
  • failed · object[] — required
    • path · string — required
    • message · string — required
  • notes · object[] — required
    • code · "people-as-text" | "relations-as-text" | "formulas-as-text" | "ambiguous-dates-as-text" | "numbers-as-text" | "fields-not-created" | "field-key-collision" | "timestamps-not-preserved" | "authors-not-preserved" | "views-not-imported" | "comments-not-imported" | "row-body-unmatched" | "links-unresolved" | "storage-limit-reached" | "source-urls-not-rewritten" | "notion-blocks-unsupported" | "notion-formatting-lost" | "notion-files-not-transferred" | "confluence-macros-unsupported" | "confluence-attachments-skipped" | "confluence-attachments-quota" | "confluence-mentions-not-imported" — required
    • count · integer — required, -9007199254740991–9007199254740991
    • samples · string[] — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

GET /api/v1/workspaces/{workspaceId}/import/runs

The history of imports into this workspace, newest first. One entry per run, whatever the run was fed by. Runs whose target collection the caller may not read are omitted rather than refused — the history is a view of the collections you can see, not a second way to enumerate them.

listImportRuns · token scope: content:read

Path parameters

  • workspaceId · string (uuid) — required

Query parameters

  • cursor · string — length 1–∞
  • limit · integer — 1–100

Response 200application/json

  • items · object[] — required
    • id · string (uuid) — required
    • source · "basalt" | "notion" | "markdown" | "confluence" — required
    • status · "complete" | "partial" — required
    • collectionId · string (uuid) — required
    • collectionName · string — required
    • startedBy · object | null — required
      • id · string (uuid) — required
      • name · string | null — required
    • startedAt · string (date-time) — required
    • finishedAt · string (date-time) — required
    • counts · object — required
      • pages · integer — required, -9007199254740991–9007199254740991
      • databases · integer — required, -9007199254740991–9007199254740991
      • rows · integer — required, -9007199254740991–9007199254740991
      • fieldsCreated · integer — required, -9007199254740991–9007199254740991
      • fieldsReused · integer — required, -9007199254740991–9007199254740991
      • attachments · integer — required, -9007199254740991–9007199254740991
      • linksRewritten · integer — required, -9007199254740991–9007199254740991
      • linksUnresolved · integer — required, -9007199254740991–9007199254740991
    • linkCount · integer — required, -9007199254740991–9007199254740991
    • linksTruncated · boolean — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

GET /api/v1/workspaces/{workspaceId}/import/runs/{runId}

One import run with its full report: counts, the decisions taken on your behalf, skipped files, failures and a bounded sample of unresolved links. Requires read on the collection it imported into. The link map is a separate, paginated resource — see /links.

getImportRun · token scope: content:read

Path parameters

  • workspaceId · string (uuid) — required
  • runId · string (uuid) — required

Response 200application/json

  • id · string (uuid) — required
  • source · "basalt" | "notion" | "markdown" | "confluence" — required
  • status · "complete" | "partial" — required
  • collectionId · string (uuid) — required
  • collectionName · string — required
  • startedBy · object | null — required
    • id · string (uuid) — required
    • name · string | null — required
  • startedAt · string (date-time) — required
  • finishedAt · string (date-time) — required
  • counts · object — required
    • pages · integer — required, -9007199254740991–9007199254740991
    • databases · integer — required, -9007199254740991–9007199254740991
    • rows · integer — required, -9007199254740991–9007199254740991
    • fieldsCreated · integer — required, -9007199254740991–9007199254740991
    • fieldsReused · integer — required, -9007199254740991–9007199254740991
    • attachments · integer — required, -9007199254740991–9007199254740991
    • linksRewritten · integer — required, -9007199254740991–9007199254740991
    • linksUnresolved · integer — required, -9007199254740991–9007199254740991
  • linkCount · integer — required, -9007199254740991–9007199254740991
  • linksTruncated · boolean — required
  • report · object — required
    • formatVersion · 2 — required
    • runId · string (uuid) — required
    • status · "complete" | "partial" — required
    • source · "basalt" | "notion" | "markdown" | "confluence" — required
    • collectionId · string (uuid) — required
    • collectionName · string — required
    • collectionCreated · boolean — required
    • counts · object — required
      • pages · integer — required, -9007199254740991–9007199254740991
      • databases · integer — required, -9007199254740991–9007199254740991
      • rows · integer — required, -9007199254740991–9007199254740991
      • fieldsCreated · integer — required, -9007199254740991–9007199254740991
      • fieldsReused · integer — required, -9007199254740991–9007199254740991
      • attachments · integer — required, -9007199254740991–9007199254740991
      • linksRewritten · integer — required, -9007199254740991–9007199254740991
      • linksUnresolved · integer — required, -9007199254740991–9007199254740991
    • unresolved · object[] — required
      • from · string — required
      • target · string — required
    • skipped · object[] — required
      • path · string — required
      • reason · "unsupported-file" | "unsupported-image" | "html-export" | "too-large" | "empty" | "duplicate-view-csv" — required
    • failed · object[] — required
      • path · string — required
      • message · string — required
    • notes · object[] — required
      • code · "people-as-text" | "relations-as-text" | "formulas-as-text" | "ambiguous-dates-as-text" | "numbers-as-text" | "fields-not-created" | "field-key-collision" | "timestamps-not-preserved" | "authors-not-preserved" | "views-not-imported" | "comments-not-imported" | "row-body-unmatched" | "links-unresolved" | "storage-limit-reached" | "source-urls-not-rewritten" | "notion-blocks-unsupported" | "notion-formatting-lost" | "notion-files-not-transferred" | "confluence-macros-unsupported" | "confluence-attachments-skipped" | "confluence-attachments-quota" | "confluence-mentions-not-imported" — required
      • count · integer — required, -9007199254740991–9007199254740991
      • samples · string[] — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

The link map of one run: old link → new id, one row per way the thing used to be addressed (an archive path, a notion.so URL, an Obsidian [[wikilink]], a source-workspace id). This is what you point at the systems that still link to the old location. Filter with q (substring of the old link or the title) and sourceKind; paginate with cursor.

listImportRunLinks · token scope: content:read

Path parameters

  • workspaceId · string (uuid) — required
  • runId · string (uuid) — required

Query parameters

  • cursor · string — length 1–∞
  • limit · integer — 1–100
  • q · string — length 1–200
  • sourceKind · "archive-path" | "notion-url" | "wikilink" | "basalt-id" | "remote-url"

Response 200application/json

  • items · object[] — required
    • sourceKind · "archive-path" | "notion-url" | "wikilink" | "basalt-id" | "remote-url" — required
    • source · string — required
    • targetKind · "page" | "database" — required
    • targetId · string (uuid) — required
    • title · string — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

GET /api/v1/workspaces/{workspaceId}/tokens

The caller's own tokens in this workspace, newest first, including revoked and expired ones so a person can see what a credential has been doing. Never carries a secret. Session-only.

listTokens · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required

Query parameters

  • cursor · string — length 1–∞
  • limit · integer — 1–100

Response 200application/json

  • items · object[] — required
    • id · string (uuid) — required
    • workspaceId · string (uuid) — required
    • userId · string (uuid) — required
    • name · string — required
    • scopes · "content:read" | "content:write" | "admin:read" | "admin:write"[] — required
    • preview · string — required
    • createdAt · string (date-time) — required
    • expiresAt · string (date-time) — required
    • lastUsedAt · string (date-time) | null — required
    • revokedAt · string (date-time) | null — required
    • revokedReason · "manual" | "membership_lost" | null — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces/{workspaceId}/tokens

Mint a personal access token for the caller in this workspace. The response is the ONLY place the secret ever appears — it is stored as a SHA-256 digest and is not recoverable. The token acts as the caller: it can never do more than they can, and its scopes narrow that further. Session-only: a token may not mint a token.

createToken · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required

Request bodyapplication/json, required

  • name · string — required, length 1–200
  • scopes · "content:read" | "content:write" | "admin:read" | "admin:write"[] — required, 1–4 items
  • expiresInDays · integer — 1–365

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • userId · string (uuid) — required
  • name · string — required
  • scopes · "content:read" | "content:write" | "admin:read" | "admin:write"[] — required
  • preview · string — required
  • createdAt · string (date-time) — required
  • expiresAt · string (date-time) — required
  • lastUsedAt · string (date-time) | null — required
  • revokedAt · string (date-time) | null — required
  • revokedReason · "manual" | "membership_lost" | null — required
  • token · string — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

DELETE /api/v1/workspaces/{workspaceId}/tokens/{tokenId}

Revoke one token immediately. The row is kept, stamped revokedAt, so the listing still explains what existed and when it last ran. Session-only.

revokeToken · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required
  • tokenId · string (uuid) — required

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • userId · string (uuid) — required
  • name · string — required
  • scopes · "content:read" | "content:write" | "admin:read" | "admin:write"[] — required
  • preview · string — required
  • createdAt · string (date-time) — required
  • expiresAt · string (date-time) — required
  • lastUsedAt · string (date-time) | null — required
  • revokedAt · string (date-time) | null — required
  • revokedReason · "manual" | "membership_lost" | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

GET /api/v1/workspaces/{workspaceId}/webhooks

The caller's own endpoints in this workspace, newest first, with their failure state. Never carries a secret. Session-only.

listWebhooks · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required

Query parameters

  • cursor · string — length 1–∞
  • limit · integer — 1–100

Response 200application/json

  • items · object[] — required
    • id · string (uuid) — required
    • workspaceId · string (uuid) — required
    • ownerId · string (uuid) — required
    • url · string (uri) — required
    • events · "workspace" | "collection" | "page" | "outline" | "field" | "database" | "db_view" | "db_row" | "permission" | "group" | "invitation" | "comment"[] — required
    • secretPreview · string — required
    • enabled · boolean — required
    • disabledReason · "manual" | "delivery_failures" | "owner_lost_access" | null — required
    • consecutiveFailures · integer — required, -9007199254740991–9007199254740991
    • lastDeliveryAt · string (date-time) | null — required
    • lastSuccessAt · string (date-time) | null — required
    • createdAt · string (date-time) — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces/{workspaceId}/webhooks

Register an outbound endpoint for workspace events. The payload carries ids and timings, never content, and every frame naming a block is resolved against the endpoint OWNER before it is queued (ADR-0016 D9b). Needs the member role: making the server issue HTTP requests is a workspace-level act, not a per-page one. The response is the only place the signing secret appears. Session-only.

createWebhook · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required

Request bodyapplication/json, required

  • url · string (uri) — required, length 0–2000
  • events · "workspace" | "collection" | "page" | "outline" | "field" | "database" | "db_view" | "db_row" | "permission" | "group" | "invitation" | "comment"[] — required, 1–∞ items

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • ownerId · string (uuid) — required
  • url · string (uri) — required
  • events · "workspace" | "collection" | "page" | "outline" | "field" | "database" | "db_view" | "db_row" | "permission" | "group" | "invitation" | "comment"[] — required
  • secretPreview · string — required
  • enabled · boolean — required
  • disabledReason · "manual" | "delivery_failures" | "owner_lost_access" | null — required
  • consecutiveFailures · integer — required, -9007199254740991–9007199254740991
  • lastDeliveryAt · string (date-time) | null — required
  • lastSuccessAt · string (date-time) | null — required
  • createdAt · string (date-time) — required
  • secret · string — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

PATCH /api/v1/workspaces/{workspaceId}/webhooks/{webhookId}

Change an endpoint. A new URL is re-checked against the SSRF policy. Re-enabling an endpoint the server disabled also clears its failure counter. Session-only.

updateWebhook · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required
  • webhookId · string (uuid) — required

Request bodyapplication/json, required

  • url · string (uri) — length 0–2000
  • events · "workspace" | "collection" | "page" | "outline" | "field" | "database" | "db_view" | "db_row" | "permission" | "group" | "invitation" | "comment"[] — 1–∞ items
  • enabled · boolean

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • ownerId · string (uuid) — required
  • url · string (uri) — required
  • events · "workspace" | "collection" | "page" | "outline" | "field" | "database" | "db_view" | "db_row" | "permission" | "group" | "invitation" | "comment"[] — required
  • secretPreview · string — required
  • enabled · boolean — required
  • disabledReason · "manual" | "delivery_failures" | "owner_lost_access" | null — required
  • consecutiveFailures · integer — required, -9007199254740991–9007199254740991
  • lastDeliveryAt · string (date-time) | null — required
  • lastSuccessAt · string (date-time) | null — required
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

DELETE /api/v1/workspaces/{workspaceId}/webhooks/{webhookId}

Delete an endpoint and its delivery history. Session-only.

deleteWebhook · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required
  • webhookId · string (uuid) — required

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • ownerId · string (uuid) — required
  • url · string (uri) — required
  • events · "workspace" | "collection" | "page" | "outline" | "field" | "database" | "db_view" | "db_row" | "permission" | "group" | "invitation" | "comment"[] — required
  • secretPreview · string — required
  • enabled · boolean — required
  • disabledReason · "manual" | "delivery_failures" | "owner_lost_access" | null — required
  • consecutiveFailures · integer — required, -9007199254740991–9007199254740991
  • lastDeliveryAt · string (date-time) | null — required
  • lastSuccessAt · string (date-time) | null — required
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces/{workspaceId}/webhooks/{webhookId}/secret

Mint a new signing secret and return it once. Deliveries already queued are signed with the new secret — there is no overlap window, so rotate when the receiver is ready to accept it. Session-only.

rotateWebhookSecret · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required
  • webhookId · string (uuid) — required

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • ownerId · string (uuid) — required
  • url · string (uri) — required
  • events · "workspace" | "collection" | "page" | "outline" | "field" | "database" | "db_view" | "db_row" | "permission" | "group" | "invitation" | "comment"[] — required
  • secretPreview · string — required
  • enabled · boolean — required
  • disabledReason · "manual" | "delivery_failures" | "owner_lost_access" | null — required
  • consecutiveFailures · integer — required, -9007199254740991–9007199254740991
  • lastDeliveryAt · string (date-time) | null — required
  • lastSuccessAt · string (date-time) | null — required
  • createdAt · string (date-time) — required
  • secret · string — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

GET /api/v1/workspaces/{workspaceId}/webhooks/{webhookId}/deliveries

Recent delivery attempts for one endpoint, newest first — what was sent, what came back, and when the next attempt is due. Session-only.

listWebhookDeliveries · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required
  • webhookId · string (uuid) — required

Query parameters

  • cursor · string — length 1–∞
  • limit · integer — 1–100

Response 200application/json

  • items · object[] — required
    • id · string (uuid) — required
    • endpointId · string (uuid) — required
    • resource · string — required
    • resourceId · string (uuid) — required
    • status · "pending" | "delivered" | "dead" — required
    • attempt · integer — required, -9007199254740991–9007199254740991
    • responseStatus · integer | null — required
    • error · string | null — required
    • nextAttemptAt · string (date-time) | null — required
    • createdAt · string (date-time) — required
    • deliveredAt · string (date-time) | null — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

GET /api/v1/workspaces/{workspaceId}/import-connections

The workspace’s saved import connections. Credentials are never included — only a hint.

listImportConnections · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required

Response 200application/json

  • items · object[] — required
    • id · string (uuid) — required
    • workspaceId · string (uuid) — required
    • collectionId · string (uuid) — required
    • name · string — required
    • provider · "confluence" | "notion" — required
    • baseUrl · string — required
    • account · string — required
    • credentialHint · string — required
    • spaces · string[] — required
    • createdAt · string (date-time) — required
    • lastRunAt · string (date-time) | null — required
    • lastRunId · string (uuid) | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces/{workspaceId}/import-connections

Save a connection to a remote system (Confluence or Notion). Both are read-only: nothing Basalt does can change the connected system. baseUrl and account are required for Confluence and ignored for Notion, which has one address and a bearer token. The credential is stored encrypted and is never returned by any route.

createImportConnection · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required

Request bodyapplication/json, required

  • name · string — required, length 1–120
  • provider · "confluence" | "notion" — required
  • baseUrl · string (uri)
  • account · string — length 1–320
  • credential · string — required, length 1–2048
  • collectionId · string (uuid) — required
  • spaces · string[] — 0–100 items

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • collectionId · string (uuid) — required
  • name · string — required
  • provider · "confluence" | "notion" — required
  • baseUrl · string — required
  • account · string — required
  • credentialHint · string — required
  • spaces · string[] — required
  • createdAt · string (date-time) — required
  • lastRunAt · string (date-time) | null — required
  • lastRunId · string (uuid) | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

DELETE /api/v1/workspaces/{workspaceId}/import-connections/{connectionId}

Delete a connection. Imported pages and past run reports are kept.

deleteImportConnection · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required
  • connectionId · string (uuid) — required

Response 200application/json

  • ok · true — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces/{workspaceId}/import-connections/{connectionId}/probe

Check the credential and list what it can read — Confluence spaces, or the Notion pages and databases the integration has been shared with — without importing anything. A POST because it makes an outbound request on the caller’s behalf.

probeImportConnection · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required
  • connectionId · string (uuid) — required

Response 200application/json

  • ok · boolean — required
  • spaces · object[] — required
    • key · string — required
    • name · string — required
  • message · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces/{workspaceId}/import-connections/{connectionId}/run

Import from the connection now, reading only. Convergent: ids are derived from the source path, so a re-run updates the pages it created before instead of duplicating them — but a page RENAMED or moved in the source system is a new path and therefore a new page, which the run’s link map shows. Answers the run report, the same shape an uploaded import produces.

runImportConnection · token scope: session only

Path parameters

  • workspaceId · string (uuid) — required
  • connectionId · string (uuid) — required

Response 200application/json

  • runId · string (uuid) — required
  • pageCount · integer — required, -9007199254740991–9007199254740991
  • truncated · boolean — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required