Skip to main content

API Reference

This reference covers the Walrus Console external API: the endpoints third-party developers use to manage spaces, buckets, and files, plus the Seal sponsorship endpoints that back private-bucket access control.

info

This documents the current Testnet surface. The API is in alpha and currently only available on Testnet at https://api.testnet.harbor.walrus.xyz. The endpoint structure might change before Mainnet GA. For a guided walkthrough of the full encrypted flow, start with the Quick Start. For the product model behind these endpoints, see the concepts and overview.

Authentication

Every request carries an API key as a bearer token:

Authorization: Bearer hbr_…

Mint keys through the Console web interface. Each key has one of two roles:

  • read_write keys can change state: create and delete buckets, upload and delete files, and finalize private buckets.
  • read_only keys can list, check status, and download only. Any write with a read-only key returns 403 with code read_only_api_key.

A request with no valid key returns 401. A key without access to the target resource returns 403.

Base URL and conventions

All paths are relative to the base URL and prefixed with /api/v1:

https://api.testnet.harbor.walrus.xyz

Request and response bodies are JSON unless noted (file upload uses multipart/form-data, and download returns a raw byte stream). Resource identifiers are UUIDs.

Pagination

List endpoints return an opaque cursor. Pass limit to set page size and cursor to fetch the next page. When there are no further results, the cursor field returns null. Bucket lists return the cursor as next_cursor; file lists return it under pagination.nextCursor.

Asynchronous operations

Upload and delete operations are asynchronous. Upload returns 202 with a file ID, then you poll the file status endpoint until the state is completed. Delete returns 204 immediately and releases storage after a background worker confirms the removal.

Errors

Error responses use the shape:

{ "error": "Human-readable message", "code": "machine_readable_code" }

The code field is present when a machine-readable value applies. Common error codes include:

CodeMeaning
unauthorizedMissing or invalid API key.
read_only_api_keyA write was attempted with a read-only key.
api_key_registering / api_key_revoking / api_key_revokedThe key is not currently usable.
bucket_not_in_scopeThe key cannot access the target bucket.
bucket_not_finalizedThe bucket has not completed the finalize step.
mirror_missing_grantThe onchain access grant has not yet mirrored into the access index. Retry.
quota_exceededThe operation exceeds the plan or storage quota.
payload_too_largeThe upload exceeds the size limit.
bad_requestThe request failed validation.
USED_NONCE / EXPIRED_TIMESTAMP / ADDRESS_MISMATCH / INVALID_CHALLENGEWallet challenge errors, returned by the signature-authentication flow.

Create-bucket requests that exceed a plan limit return 422 with a distinct shape:

{ "code": "PLAN_LIMIT_EXCEEDED", "limit": "storage", "currentTier": "free" }

The limit value is one of storage, users, or buckets.

Spaces

A space is the top-level container for your data. Console creates a Personal Space at sign-up.

List spaces

GET /api/v1/spaces

Lists all spaces accessible to the authenticated user.

ParameterLocationRequiredDescription
typequeryNoFilter by space type: personal or team.

Returns 200 with a data array of spaces. Each space includes id, type, name, storage_used, storage_cap, bucket_count, role, and created_at. Team Spaces also include member_count.

List buckets in a space

GET /api/v1/spaces/{id}/buckets

ParameterLocationRequiredDescription
idpathYesSpace UUID.
limitqueryNoPage size, 1 to 1000. Default 100.
cursorqueryNoCursor from a previous page.
qqueryNoCase-insensitive substring match on bucket name.
visibilityqueryNoFilter by public or private.
sortFieldqueryNoname, date, or storage. Default date.
sortOrderqueryNoasc or desc. Default desc.

Returns 200 with buckets and next_cursor.

Create a bucket

POST /api/v1/spaces/{id}/buckets

Reserves a private bucket. This is the first step of the reserve, sign, and finalize handshake. In the current alpha, scope accepts private only.

Body:

{ "name": "secrets", "scope": "private" }

Returns 201 with the reservation:

{
"bucket_id": "…",
"bytes": "<base64 sponsored Sui transaction>",
"digest": "…",
"state": "pending_policy"
}

Sign bytes locally with your service key, then call the finalize endpoint. The bucket stays in pending_policy and accepts no uploads until finalize succeeds. A name conflict returns 409. A plan-limit breach returns 422. See the Quick Start for the full signing flow.

List files in a space

GET /api/v1/spaces/{id}/files

Searches files across every bucket in the space.

ParameterLocationRequiredDescription
idpathYesSpace UUID.
limitqueryNoPage size, 1 to 100. Default 20.
cursorqueryNoCursor from a previous page.
qqueryNoCase-insensitive substring match on file name.
sortFieldqueryNoname, date, size, or type. Default date.
sortOrderqueryNoasc or desc. Default desc.

Returns 200 with data and a pagination object (limit, hasMore, nextCursor).

Buckets

A bucket holds files inside a space. Private buckets store Seal ciphertext only.

Get a bucket

GET /api/v1/buckets/{id}

Returns 200 with the bucket under data: id, space_id, name, oyster_bucket_name, visibility, seal_policy_id, storage_used, created_at, and updated_at.

Update a bucket

PUT /api/v1/buckets/{id}

FieldRequiredDescription
nameYesNew bucket name, 1 to 100 characters in length.
sealPolicyIdNoSeal policy ID. Pass null to clear.
visibilityNoImmutable in v1. Changing it returns 403.

Returns 200 with the updated bucket (id, name, visibility, updated_at). A name conflict returns 409.

Delete a bucket

DELETE /api/v1/buckets/{id}?confirm=true

Requires the confirm=true query parameter, and the bucket must be empty. Returns 204 with an X-Deleted-Id header. A missing confirmation, a validation error, or a non-empty bucket returns 400.

Finalize a private bucket

POST /api/v1/buckets/{id}/finalize

Submits the signature over the reserved transaction bytes to activate a pending private bucket.

Body:

{ "signature": "<base64 signature>" }

Returns 200:

{ "bucket_id": "…", "seal_policy_id": "…", "state": "active" }

seal_policy_id is the onchain bucket-policy object ID used by Seal for access checks. The bucket is now ready for uploads.

List files in a bucket

GET /api/v1/buckets/{id}/files

Takes the same limit, cursor, q, sortField, and sortOrder parameters as the space file listing. Returns 200 with data and pagination.

Upload a file

POST /api/v1/buckets/{id}/files

Uploads a file asynchronously using multipart/form-data.

FieldRequiredDescription
fileYesThe file bytes. For private buckets, upload the Seal-encrypted object.
nameNoFile name, up to 255 characters in length.
metadataNoJSON object string persisted with the file. Maximum 8 KiB.

Returns 202 with the file summary under data. Poll the status endpoint until the state is completed.

info

After finalize, the onchain access grant takes a few seconds to propagate to the access index. During this window, uploads fail with a 403 and the error code mirror_missing_grant. Retry every few seconds until the grant appears.

Other responses include 409 (bucket not finalized or duplicate file name), 413 (payload too large), 422 (quota exceeded), and 429 (rate limited).

Files

Get file metadata

GET /api/v1/buckets/{id}/files/{fileId}

Returns 200 with the file summary under data: id, bucket_id, name, blob_id, oyster_object_id, size, mime_type, status, is_private, metadata, created_at, updated_at, and deleted_at.

Delete a file

DELETE /api/v1/buckets/{id}/files/{fileId}

Marks the file as deleting and enqueues a background job to remove the underlying blob. Returns 204 immediately. Storage is released after the worker confirms the delete. Repeated calls for the same file ID are deduplicated.

Get upload status

GET /api/v1/buckets/{id}/files/{fileId}/status

Polls the state of an in-flight upload after the upload endpoint returns 202.

Returns 200 with data.state, one of queued, active, completed, or failed. When the worker reports it, data.progress gives fractional progress from 0 to 1. When the state is failed, data.error includes a code and message. Once the job leaves the queue this endpoint returns 404, which does not mean the upload failed. If you get a 404 after uploading, check the file metadata endpoint to confirm the final status before treating it as an error.

Download a file

GET /api/v1/buckets/{id}/files/{fileId}/download

Streams the raw file content with Content-Type and Content-Disposition headers. For a private bucket, this returns the Seal ciphertext, which you decrypt client-side. See the Quick Start for the decrypt flow.

A file in deleting or deleted status returns 423. An unavailable blob stream returns 500.

Seal sponsorship

These endpoints build and sponsor the onchain bucket-policy transactions that manage private access. Console sponsors the gas through Enoki, so your service key signs but does not need a token balance. They are typically driven through the SDK patterns shown in the Quick Start.

POST /api/v1/seal/sponsor

Builds a sponsored bucket-policy transaction and returns the bytes to sign plus a digest to echo to the execute endpoint. The body's kind selects the operation:

kindPurposeAdditional fields
bucket_group_createCreate a bucket access group.bucketId
grant_bucket_accessGrant access to recipients.groupIds, recipientAddress, scope (read or readwrite)
share_adminAdd an admin to a group.groupId, member
unshareRemove a member from a group.groupId, member
unshare_bucket_accessRevoke a service signer's access.groupId, serviceSignerAddress

Returns 200 with bytes and digest.

Execute a sponsored transaction

POST /api/v1/seal/sponsor/{digest}/execute

Submits the signature over the sponsored bytes. Console broadcasts the transaction and returns the onchain digest.

ParameterLocationRequiredDescription
digestpathYesThe sponsor digest from the sponsor endpoint.

Body:

{ "signature": "<base64 signature>" }

Returns 200 with the onchain digest. An expired or unknown digest returns 404.