vee.kr

REST

API v1

Complete bearer-authenticated JSON API for links, markdown, files, images, collections, Notion mirrors, bio pages, and HTML artifacts. OpenAPI is the machine-readable companion.

Authentication

Create a key in the dashboard, then send it on every request except the public OpenAPI download. The plaintext is shown once at creation; the server stores sha256(plaintext).

Authorization: Bearer bdma_<key>
Scope Allows
readGET endpoints (list, detail, QR, analytics, markdown versions, jobs).
writeCreate, update, upload, redeploy, restore, and optimize endpoints.
deleteSoft-delete a resource you own.
  • invalid_api_key — Missing, malformed, unknown, or revoked bearer key.
  • user_disabled — The owning user account is disabled.
  • insufficient_scope — The key lacks read, write, or delete.

Conventions

  • Errors are JSON {error, error_code, field?, details?}. error is human-readable; error_code is the stable machine token.
  • Rate limiting is per identity (API key id, else IP). Responses may carry X-RateLimit-* headers; exhaustion returns 429, Retry-After, and rate_limited.
  • Ordinary JSON mutations are capped at 1 MiB. Uploads follow MAX_UPLOAD_MB; artifacts have separate ZIP/file/count ceilings. MCP POST /mcp JSON allows up to 12 MiB for base64 payloads.
  • GET /resources returns a weak list ETag. GET /resources/{slug} returns an editable-state ETag; PATCH accepts If-Match and returns 412 etag_mismatch when stale.
  • Deleted, disabled-owner, and expired resources return 404 not_found (no existence leak). Past max_views uses the configured expired state.
  • Every endpoint is scoped to the authenticated user's own resources; another owner's slug returns 404.
  • Owner requests never increment hit_count or create page_views rows.
  • List endpoints are offset-paginated. When next_cursor is present, pass it back as ?cursor=; cursor takes precedence over offset.

Quickstart

Confirm the key, then create a short link:

curl -sS \
  -H "Authorization: Bearer $REST_API_KEY" \
  https://<your-host>/api/v1/me
curl -sS \
  -H "Authorization: Bearer $REST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"link","target_url":"https://example.com","title":"Example"}' \
  https://<your-host>/api/v1/resources

Endpoints

Every registered /api/v1 route is listed below with its required scope. The OpenAPI download mirrors the same paths for codegen and exact JSON Schema.

Meta

Method Path Scope Description
GET/api/v1/mereadAccount profile, storage quota, and feature flags (including features.artifacts_enabled).
GET/api/v1/openapi.yamlPublic OpenAPI 3.1 document (IP rate-limited).

Remote MCP is OAuth-only and documented separately: MCP.

Resources and uploads

Method Path Scope Description
GET/api/v1/resourcesreadPaginated owned resources. Query: type, q, sort, label, archived, pinned, cursor, offset, limit. Returns a weak ETag and counts.
GET/api/v1/resources/{slug}readFull editable detail with an ETag for later If-Match.
POST/api/v1/resourceswriteJSON create for link, markdown, collection, notion, or bio (type discriminator).
POST/api/v1/resources/bulk-createwriteCreate up to 200 links independently with idempotent retries.
POST/api/v1/resources/importwriteImport Bitly or Linktree links from a UTF-8 CSV.
PATCH/api/v1/resources/{slug}writePartial update; optional If-Match → 412 etag_mismatch; slug conflict → 409 slug_taken.
DELETE/api/v1/resources/{slug}deleteSoft-delete (deleted_at).
GET/api/v1/resources/{slug}/qrreadQR image for the public URL (?format=png|svg, ?size, ?src=qr).
POST/api/v1/links/simulatereadDry-run destination selection for a draft link (rules and A/B variants).
POST/api/v1/resources/{slug}/link/simulatereadDry-run destination selection for a saved link.
POST/api/v1/uploadswritemultipart file upload; sniffed MIME creates a file or image resource.
POST/api/v1/artifactswritemultipart HTML artifact publish (metadata JSON + ZIP or mapped files).
GET/api/v1/resources/{slug}/artifact/filesreadList the current deployment's canonical files and text-editability metadata.
GET/api/v1/resources/{slug}/artifact/sourcereadRead one verified UTF-8 source file (up to 1 MiB) in a no-store JSON response.
POST/api/v1/resources/{slug}/artifact/deploymentswritePublish a full replacement or sparse immutable artifact deployment; optional If-Match.
POST/api/v1/resources/{slug}/collection/itemswriteAppend one owned file or image resource to the collection.
PATCH/api/v1/resources/{slug}/collectionwriteBatch-update collection item positions and display copy.
DELETE/api/v1/resources/{slug}/collection/items/{itemID}deleteSoft-delete one collection item.
PATCH/api/v1/resources/{slug}/biowriteReplace the full bio profile and page/block snapshot; pages is required.
POST/api/v1/resources/{slug}/bio/avatarwriteUpload or replace the bio profile avatar or cover.
DELETE/api/v1/resources/{slug}/bio/avatardeleteDelete the bio profile avatar or cover.
POST/api/v1/resources/{slug}/bio/coverwriteUpload or replace the bio profile avatar or cover.
DELETE/api/v1/resources/{slug}/bio/coverdeleteDelete the bio profile avatar or cover.
POST/api/v1/resources/{slug}/bio/pages/{pageID}/blocks/filewriteCreate a file- or image-backed bio block with multipart upload.
POST/api/v1/resources/{slug}/bio/blocks/{blockID}/filewriteReplace the binary attached to an existing file-backed bio block.
POST/api/v1/resources/{slug}/bio/blocks/{blockID}/thumbnailwriteUpload or replace a bio block thumbnail.
GET/api/v1/resources/{slug}/bio/form-submissionsreadList bio form submissions.
GET/api/v1/resources/{slug}/bio/form-submissions/exportreadExport bio form submissions as CSV.
DELETE/api/v1/resources/{slug}/bio/form-submissions/{submissionID}deleteDelete one bio form submission.

Markdown versions

Method Path Scope Description
GET/api/v1/resources/{slug}/markdown/versionsreadMarkdown version history (newest first; retained to 50).
GET/api/v1/resources/{slug}/markdown/versions/{versionID}readOne historical markdown snapshot.
POST/api/v1/resources/{slug}/markdown/versions/{versionID}/restorewriteRestore a version atomically; optional If-Match; returns a new ETag.

Jobs, optimize, and analytics

Method Path Scope Description
POST/api/v1/resources/{slug}/file/optimize-originalwriteQueue original-file optimization (202). Eligible: large JPEG or HEIC/HEIF.
POST/api/v1/resources/{slug}/notion/refreshwriteQueue a full-document or single-page Notion refresh.
GET/api/v1/resources/{slug}/notion/pagesreadList cached Notion page summaries and refresh status.
GET/api/v1/resources/{slug}/notion/pages/{page_id}readRead one cached Notion page summary and rendered HTML.
GET/api/v1/resources/{slug}/notion/assetsreadList safe metadata and public URLs for cached Notion assets.
GET/api/v1/jobsreadList owned background jobs (?status, ?type, ?limit, ?cursor, ?offset).
GET/api/v1/jobs/{id}readOne job's status, result, error, attempts, and suggested next poll delay.
POST/api/v1/jobs/{id}/retrywriteRetry one owned failed or skipped job and return a new accepted job.
GET/api/v1/upload-requestsreadList owned upload requests without plaintext tokens.
POST/api/v1/upload-requestswriteCreate an upload request; the plaintext token is returned once.
GET/api/v1/upload-requests/{id}readRead one owned upload request without its plaintext token.
PATCH/api/v1/upload-requests/{id}writePartially update metadata while preserving omitted fields.
POST/api/v1/upload-requests/{id}/statuswriteSet lifecycle status to open, paused, or completed.
POST/api/v1/upload-requests/{id}/rotate-tokenwriteInvalidate the previous token and return its replacement once.
GET/api/v1/upload-requests/{id}/submissionsreadList submissions received by an owned upload request.
GET/api/v1/upload-requests/{id}/submissions/{submissionID}readRead one submission and safe metadata for its resources.
PATCH/api/v1/upload-requests/{id}/submissions/{submissionID}writeUpdate the review status of one owned submission.
POST/api/v1/upload-requests/{id}/revokedeleteRevoke one owned upload request.
GET/api/v1/analytics/resources/{slug}readHits, visitors, daily counts, ranked buckets, optional bio_clicks.
GET/api/v1/analytics/resources/{slug}/exportreadHits, visitors, daily counts, ranked buckets, optional bio_clicks.
GET/api/v1/analytics/exportreadHits, visitors, daily counts, ranked buckets, optional bio_clicks.

Webhooks

Method Path Scope Description
GET/api/v1/webhooksreadManage signed webhook endpoints and inspect delivery state.
POST/api/v1/webhookswriteManage signed webhook endpoints and inspect delivery state.
PATCH/api/v1/webhooks/{id}writeManage signed webhook endpoints and inspect delivery state.
POST/api/v1/webhooks/{id}/rotate-secretwriteManage signed webhook endpoints and inspect delivery state.
POST/api/v1/webhooks/{id}/enablewriteManage signed webhook endpoints and inspect delivery state.
POST/api/v1/webhooks/{id}/disablewriteManage signed webhook endpoints and inspect delivery state.
POST/api/v1/webhooks/{id}/testwriteManage signed webhook endpoints and inspect delivery state.
GET/api/v1/webhooks/{id}/deliveriesreadManage signed webhook endpoints and inspect delivery state.

Creating resources

POST /api/v1/resources accepts a type-discriminated JSON body. Common optional fields on all creates: slug (auto 8-char if omitted), title, description, visibility, password, expires_at, labels.

type Required type fields Optional type fields
linktarget_urlmax_views
markdownraw_markdown or body_markdownmax_views
collection
notionsource_url or fetch_urlroot_page_id
biodisplay_name, headline, bio, theme

Successful creates return 201 with the type-specific ResourceDetail used by GET /resources/{slug}.

  • POST /api/v1/uploads uses multipart field file plus the common create fields. max_views is accepted only for non-image files.
  • POST /api/v1/artifacts uses multipart metadata (JSON) plus either archive (ZIP) or declared file parts. max_views is rejected for artifacts.

HTML artifacts

Artifacts upload a static site. The share URL stays https://<host>/<slug>; after access checks Bidama hands the browser to https://<origin_label>.ar.phi.kr. metadata is a JSON object in the multipart field of the same name:

{
  "slug": "optional-slug",
  "title": "Optional title",
  "description": "Optional description",
  "visibility": "public",
  "password": "optional password",
  "expires_at": "2026-08-01T00:00:00Z",
  "labels": ["demo"]
}
  • ZIP mode: one file part named archive; omit metadata.files and other file parts.
  • Direct mode: metadata.files is [{part, path}, …] and each declared part appears exactly once as a multipart file. ZIP and direct modes cannot be mixed.
  • Entry point: non-empty root index.html if present, else the first non-empty .html/.htm file. Paths are NFC relative POSIX paths with traversal and reserved-prefix rejection.
  • Default ceilings: 50 MiB ZIP, 50 MiB per file, 100 MiB expanded, 500 files, 200:1 expansion, 512 KiB manifest. BIDAMA_ARTIFACT_MAX_* may lower them.
  • Redeploy metadata accepts only the optional files mapping. It creates a new origin, moves the current pointer, and never reuses the previous origin_label.

Errors

Failed requests return JSON with a human message and a stable code:

{
  "error": "Slug already in use.",
  "error_code": "slug_taken",
  "field": "slug"
}
error_code HTTP Description
invalid_api_key401Missing, malformed, unknown, or revoked bearer key.
insufficient_scope403The key lacks read, write, or delete.
not_found404Resource, job, or version not found — or not owned, deleted, or expired.
slug_taken409Slug already in use.
idempotency_conflict409The idempotency key was reused with a different request.
job_already_active409An equivalent background job is already running.
etag_mismatch412If-Match did not match the current state; re-read and retry.
payload_too_large413Body or upload exceeded a size limit.
rate_limited429Too many requests; wait for Retry-After.
quota_exceeded400Storage quota exceeded.
validation_failed400Invalid field or ineligible optimize request; check field.
feature_disabled400The user's feature flag for this action is off.
artifacts_disabled503HTML Artifact creation or serving is disabled.
artifact_archive_invalid400ZIP failed validation (unsafe, encrypted, or expansion-ratio).
artifact_path_invalid400A logical path is invalid, reserved, duplicated, or colliding.
artifact_index_missing400No eligible non-empty HTML entry, or root index.html is empty.
artifact_too_large413An artifact byte, file-count, or manifest limit was exceeded.

Useful conventions

  • List pages may include next_cursor — pass it back as ?cursor=.
  • Owner reads never increment public hit counts.
  • Deleted, expired, or foreign resources look like not_found.
  • MCP is OAuth-only at /mcp; API keys are REST-only.
  • Accepted optimize jobs return 202 with job_id, poll_url, and retry_after_seconds.

Also see: MCP · OpenAPI