Skip to main content

API request and response examples

This page gives concrete examples for the public HTTP routes most integrations start with.

Use these examples to understand payload shape and workflow order. Use the generated public OpenAPI contract for exact field-level validation and any future contract changes.

Before you start

Examples below assume:

  • your base URL is https://api.aetherfs.io
  • you have an auth mechanism appropriate for your deployment
  • SESSION_ID, SOURCE_ID, and APPROVAL_ID are real values from earlier calls

Important JSON encoding note

Several public endpoints use fields declared as format: byte in OpenAPI.

In JSON, those values are represented as base64-encoded strings.

That affects routes such as:

  • session cache set/get
  • bus publish payloads

1. Create a session

Typical route:

  • POST /v1/sessions

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"sourceRef": {
"sourceId": "src_webapp",
"revisionId": "rev_2026_03_10"
},
"initialTags": {
"task": "bugfix-123",
"owner": "alice"
}
}'

Example response:

{
"id": "sess_1234abcd",
"baseFingerprint": "4e52c7c1f6d387f2b5c6d09d8124b4f877ef4d97",
"status": "SESSION_STATUS_ACTIVE",
"tags": {
"task": "bugfix-123",
"owner": "alice"
},
"createdAt": "2026-03-11T05:55:21Z",
"lastActivityAt": "2026-03-11T05:55:21Z",
"capabilities": {
"commit": true,
"approvals": true,
"annotations": true
}
}

When to use it:

  • provisioning a new workspace
  • creating one session per task
  • starting a review or automation flow from a managed source

2. Get session detail

Typical route:

  • GET /v1/sessions/{sessionId}

Example request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd' \
-H 'Authorization: Bearer <token>'

Example response:

{
"id": "sess_1234abcd",
"baseFingerprint": "4e52c7c1f6d387f2b5c6d09d8124b4f877ef4d97",
"status": "SESSION_STATUS_ACTIVE",
"tags": {
"task": "bugfix-123",
"owner": "alice"
},
"createdAt": "2026-03-11T05:55:21Z",
"lastActivityAt": "2026-03-11T06:02:11Z",
"capabilities": {
"commit": true,
"approvals": true,
"annotations": true
}
}

Use this before:

  • destructive actions
  • review-state transitions
  • commit or restore
  • rendering a session detail page

3. Read a manifest

Typical route:

  • GET /v1/sessions/{sessionId}/manifest

Example request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/manifest?pathPrefix=/src&pageSize=2&includeCasExtents=true' \
-H 'Authorization: Bearer <token>'

Example response:

{
"nodes": [
{
"path": "/src",
"fileType": "VFS_FILE_TYPE_DIRECTORY",
"sizeBytes": "0",
"contentHash": "",
"mode": 16877
},
{
"path": "/src/main.ts",
"fileType": "VFS_FILE_TYPE_FILE",
"sizeBytes": "1284",
"contentHash": "sha256:0c9c7ca53c8f7d8b1efb62fdb5879f7ff6b6e2d9f0b3f0173d9d68c8a1d62d7d",
"mode": 33188,
"casExtents": []
}
],
"nextPageToken": "eyJvZmZzZXQiOjJ9",
"manifestHash": "mf_01HRJ4D2S3SK3CFBB9K4CMQ0GE"
}

Use it for:

  • file browsers
  • review UIs
  • change previews
  • subtree loading and pagination

4. Fork a session

Typical route:

  • POST /v1/sessions/{sourceSessionId}/fork

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/fork' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"sourceCheckpointLabel": "before-review",
"newTags": {
"review-track": "proposal-b",
"owner": "reviewer-2"
}
}'

Example response:

{
"id": "sess_7890fork",
"baseFingerprint": "4e52c7c1f6d387f2b5c6d09d8124b4f877ef4d97",
"status": "SESSION_STATUS_ACTIVE",
"tags": {
"task": "bugfix-123",
"owner": "reviewer-2",
"review-track": "proposal-b"
},
"createdAt": "2026-03-11T06:10:08Z",
"lastActivityAt": "2026-03-11T06:10:08Z",
"capabilities": {
"commit": true,
"approvals": true
}
}

Use it when:

  • you need parallel proposals
  • you want a clean review branch
  • several actors may diverge from the same baseline

5. Create a checkpoint

Typical route:

  • POST /v1/sessions/{sessionId}/checkpoints

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/checkpoints' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"label": "before-large-refactor"
}'

Example response:

{
"id": "cp_5678efgh",
"sessionId": "sess_1234abcd",
"label": "before-large-refactor",
"createdAt": "2026-03-11T06:14:22Z"
}

Use it before:

  • mass edits
  • import/sync operations
  • risky agent-driven transformations
  • review handoffs

6. Add an annotation

Typical route:

  • POST /v1/sessions/{sessionId}/annotations

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/annotations' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"filePath": "/src/main.ts",
"lineNumber": 42,
"expectedContentHash": "sha256:0c9c7ca53c8f7d8b1efb62fdb5879f7ff6b6e2d9f0b3f0173d9d68c8a1d62d7d",
"comment": "This branch adds the null-check, but the error path still needs coverage."
}'

Example response:

{
"annotation": {
"id": "ann_01HRJ5Q8D3RMY8H7RWX7Y1AVRJ",
"sessionId": "sess_1234abcd",
"filePath": "/src/main.ts",
"lineNumber": 42,
"contentHash": "sha256:0c9c7ca53c8f7d8b1efb62fdb5879f7ff6b6e2d9f0b3f0173d9d68c8a1d62d7d",
"comment": "This branch adds the null-check, but the error path still needs coverage.",
"reviewerId": "alice",
"status": "ANNOTATION_STATUS_OPEN",
"createdAt": "2026-03-11T06:18:04Z"
}
}

The important field here is expectedContentHash. It prevents a comment from landing on stale content.

List annotations for a file or session

Route:

  • GET /v1/sessions/{sessionId}/annotations

Request for one file:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/annotations?filePath=/src/main.ts' \
-H 'Authorization: Bearer <token>'

Response:

{
"annotations": [
{
"id": "ann_01HRJ5Q8D3RMY8H7RWX7Y1AVRJ",
"sessionId": "sess_1234abcd",
"filePath": "/src/main.ts",
"lineNumber": 42,
"contentHash": "sha256:0c9c7ca53c8f7d8b1efb62fdb5879f7ff6b6e2d9f0b3f0173d9d68c8a1d62d7d",
"comment": "This branch adds the null-check, but the error path still needs coverage.",
"reviewerId": "alice",
"status": "ANNOTATION_STATUS_OPEN",
"createdAt": "2026-03-11T06:18:04Z"
}
]
}

If filePath is omitted, the route returns all annotations visible for the session.

Resolve an annotation

Route:

  • PATCH /v1/sessions/{sessionId}/annotations/{annotationId}

Request:

curl -X PATCH 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/annotations/ann_01HRJ5Q8D3RMY8H7RWX7Y1AVRJ' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"newStatus": "ANNOTATION_STATUS_RESOLVED"
}'

Response:

{
"annotation": {
"id": "ann_01HRJ5Q8D3RMY8H7RWX7Y1AVRJ",
"sessionId": "sess_1234abcd",
"filePath": "/src/main.ts",
"lineNumber": 42,
"contentHash": "sha256:0c9c7ca53c8f7d8b1efb62fdb5879f7ff6b6e2d9f0b3f0173d9d68c8a1d62d7d",
"comment": "This branch adds the null-check, but the error path still needs coverage.",
"reviewerId": "alice",
"status": "ANNOTATION_STATUS_RESOLVED",
"createdAt": "2026-03-11T06:18:04Z"
}
}

7. Request approval

Typical route:

  • POST /v1/sessions/{sessionId}/request-approval

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/request-approval' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"reason": "COMMIT_TO_MAIN",
"details": {
"summary": "Applies the login validation fix and updates related tests.",
"changedFiles": [
"/src/main.ts",
"/tests/main.test.ts"
]
}
}'

Example response:

{
"approvalId": "appr_01HRJ62B2S2H12XATB9TQX6W7R"
}

At this point, session state may move to SESSION_STATUS_PENDING_APPROVAL.

8. Grant or deny approval

Typical routes:

  • POST /v1/sessions/{sessionId}/approvals/{approvalId}/grant
  • POST /v1/sessions/{sessionId}/approvals/{approvalId}/deny

Grant example:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/approvals/appr_01HRJ62B2S2H12XATB9TQX6W7R/grant' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{}'

Example response:

{}

Deny example:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/approvals/appr_01HRJ62B2S2H12XATB9TQX6W7R/deny' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"feedback": "Split the large change into two commits before retrying."
}'

Example response:

{}

9. Commit a session

Typical route:

  • POST /v1/sessions/{sessionId}/commit

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/commit' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"message": "Fix login validation and add regression tests",
"author": {
"name": "Alice Example",
"email": "alice@example.com"
},
"gitBranch": {
"branchName": "fix/login-validation"
}
}'

Example response:

{
"commitId": "a6c3a92c805b11d9e4e8f0c82be2b43277a1e3d5"
}

The response is intentionally small. Treat the returned commitId as the durable reference to the result.

10. Set and get a session cache entry

Typical routes:

  • PUT /v1/sessions/{sessionId}/cache/{key}
  • GET /v1/sessions/{sessionId}/cache/{key}

Example set request:

curl -X PUT 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/cache/analysis.summary' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"value": "eyJzdGF0dXMiOiJyZWFkeS1mb3ItcmV2aWV3IiwicmlzayI6ImxvdyJ9",
"ttlSeconds": 3600
}'

The value above is base64-encoded JSON.

Decoded, it contains:

{"status":"ready-for-review","risk":"low"}

Example set response:

{}

Example get request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/cache/analysis.summary' \
-H 'Authorization: Bearer <token>'

Example get response:

{
"value": "eyJzdGF0dXMiOiJyZWFkeS1mb3ItcmV2aWV3IiwicmlzayI6ImxvdyJ9",
"found": true
}

Use the cache for:

  • derived tool output
  • workflow context
  • machine-consumable summaries
  • small structured session state

11. Publish a bus message

Typical route:

  • POST /v1/sessions/{sessionId}/bus:publish

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/bus:publish' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"topic": "review:status",
"payload": "eyJzdGF0dXMiOiJyZWFkeS1mb3ItYXBwcm92YWwifQ=="
}'

Decoded, the payload is:

{"status":"ready-for-approval"}

Example response:

{}

Use the bus for:

  • live progress
  • event-style workflow transitions
  • tool-to-tool coordination

Consume a real-time bus subscription

Route:

  • GET /v1/sessions/{sessionId}/bus

This is a long-lived streaming response. Use a streaming-capable HTTP client. For curl, use -N so output is not buffered.

Example request:

curl -N 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/bus?topics=review:status&topics=tests:complete&scope=SESSION_ONLY&replayLimit=2' \
-H 'Authorization: Bearer <token>'

Representative streamed response items:

{"result":{"messageId":"msg_01HRJ9B2P4QDXY6RKEHHX9QWF4","sessionId":"sess_1234abcd","topic":"review:status","payload":"eyJzdGF0dXMiOiJyZWFkeS1mb3ItYXBwcm92YWwifQ==","senderId":"worker-7","publishedAt":"2026-03-11T08:44:10Z"}}
{"result":{"messageId":"msg_01HRJ9B5TY3CM95D2QZVFQ3QJB","sessionId":"sess_1234abcd","topic":"tests:complete","payload":"eyJwYXNzZWQiOjEyOCwiZmFpbGVkIjowfQ==","senderId":"worker-7","publishedAt":"2026-03-11T08:44:31Z"}}

Decoded example payloads:

  • eyJzdGF0dXMiOiJyZWFkeS1mb3ItYXBwcm92YWwifQ== -> {"status":"ready-for-approval"}
  • eyJwYXNzZWQiOjEyOCwiZmFpbGVkIjowfQ== -> {"passed":128,"failed":0}

The public OpenAPI describes each streamed item as a wrapper with either:

  • result, containing a v1Message
  • error, containing a google.rpc.Status

Client guidance:

  • keep the connection open
  • treat replayLimit and replayAfterMessageId as catch-up controls
  • design your client to handle a stream, not a single JSON document

12. Create a directory

Typical route:

  • POST /v1/sessions/{sessionId}/dirs

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/dirs' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/features",
"mode": 493
}'

493 is decimal for POSIX mode 0755.

Example response:

{}

Use this before:

  • writing files into a new subtree
  • preparing a refactor destination
  • creating workflow-specific directories explicitly

13. Remove an empty directory

Typical route:

  • DELETE /v1/sessions/{sessionId}/dirs

Example request:

curl -X DELETE 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/dirs?path=/src/old-feature' \
-H 'Authorization: Bearer <token>'

Example response:

{}

This route is for empty directories. If the directory still has content, your client should remove or relocate that content first.

14. Rename or move a path

Typical route:

  • POST /v1/sessions/{sessionId}:rename

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:rename' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"sourcePath": "/src/legacy/config.ts",
"destinationPath": "/src/config/index.ts"
}'

Example response:

{}

Use this for:

  • path cleanup
  • file moves during refactors
  • directory restructuring without inventing custom path routes

15. Delete a file with optimistic concurrency

Typical route:

  • DELETE /v1/sessions/{sessionId}/files

Example request:

curl -X DELETE 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/files?path=/src/obsolete.ts&expectedHash=sha256%3A0c9c7ca53c8f7d8b1efb62fdb5879f7ff6b6e2d9f0b3f0173d9d68c8a1d62d7d' \
-H 'Authorization: Bearer <token>'

Example response:

{}

The expectedHash query parameter is the important guard here. Use it when a stale client should not be allowed to delete newer content silently.

16. Execute a batch of filesystem operations

Typical route:

  • POST /v1/sessions/{sessionId}/batch

The public batch schema currently supports three operation kinds:

  • write
  • delete
  • segmentWrite

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/batch' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"operations": [
{
"write": {
"path": "/src/generated/version.ts",
"content": "ZXhwb3J0IGNvbnN0IFZFUlNJT04gPSAiMS4yLjAiOwo=",
"expectedHash": ""
}
},
{
"delete": {
"path": "/src/obsolete.ts",
"expectedHash": "sha256:0c9c7ca53c8f7d8b1efb62fdb5879f7ff6b6e2d9f0b3f0173d9d68c8a1d62d7d"
}
},
{
"segmentWrite": {
"path": "/src/changelog.txt",
"segments": [
{
"data": {
"offset": "0",
"content": "Q2hhbmdlZCBsb2dpbiB2YWxpZGF0aW9uLgo="
}
}
],
"logicalSize": "25",
"expectedHash": "",
"mode": 33188
}
}
]
}'

Decoded examples:

  • ZXhwb3J0IGNvbnN0IFZFUlNJT04gPSAiMS4yLjAiOwo= -> export const VERSION = "1.2.0";
  • Q2hhbmdlZCBsb2dpbiB2YWxpZGF0aW9uLgo= -> Changed login validation.

Example response:

{}

Use batch when one user-visible action should become one atomic server-side action.

17. Apply sparse or incremental mutations with patchSegments

Typical route:

  • POST /v1/sessions/{sessionId}/patchSegments

This route is for partial file updates where you do not want to replace the whole file payload.

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/patchSegments' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"mutations": [
{
"path": "/src/feature.flag",
"segments": [
{
"data": {
"offset": "0",
"content": "ZW5hYmxlZD10cnVlCg=="
}
}
],
"logicalSize": "13",
"expectedHash": "sha256:2d4ed1af6f6fb9fb0f3c9ef5ec7849f9fe8f88c145d70ec4a34490e5f17f9f78",
"mode": 33188
}
],
"conflictPolicy": "SEGMENT_CONFLICT_POLICY_ABORT_ON_CONFLICT"
}'

Decoded content:

  • ZW5hYmxlZD10cnVlCg== -> enabled=true

Example response:

{
"skippedPaths": []
}

Notes:

  • expectedHash makes the mutation conditional.
  • conflictPolicy controls whether the whole request aborts or conflicting paths are skipped.
  • The schema also supports hole and borrowed segment chunks for more advanced sparse workflows.

Create route:

  • POST /v1/sessions/{sessionId}/symlinks

Create request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/symlinks' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"linkPath": "/app/current-config",
"target": "/app/configs/release-2026-03-11"
}'

Create response:

{}

List route:

  • GET /v1/sessions/{sessionId}/symlinks

List request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/symlinks?pathPrefix=/app&pageSize=50' \
-H 'Authorization: Bearer <token>'

List response:

{
"symlinks": [
{
"path": "/app/current-config",
"target": "/app/configs/release-2026-03-11"
}
],
"nextPageToken": ""
}

Read route:

  • GET /v1/sessions/{sessionId}/readSymlink

Read request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/readSymlink?path=/app/current-config' \
-H 'Authorization: Bearer <token>'

Read response:

{
"target": "/app/configs/release-2026-03-11"
}

Use these routes when your product needs explicit symlink awareness rather than treating symlinks as opaque path metadata.

Hard link route:

  • POST /v1/sessions/{sessionId}:link

Hard link request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:link' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"srcPath": "/src/main.ts",
"dstPath": "/src/main-copy.ts"
}'

Hard link response:

{}

Unlink route:

  • POST /v1/sessions/{sessionId}:unlink

Unlink request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:unlink' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main-copy.ts",
"expectedHash": "sha256:0c9c7ca53c8f7d8b1efb62fdb5879f7ff6b6e2d9f0b3f0173d9d68c8a1d62d7d"
}'

Unlink response:

{}

20. Get CAS extents for a file

Typical route:

  • POST /v1/sessions/{sessionId}:getCasExtents

Use this route when a client wants to understand which parts of a file already exist as CAS-backed extents, so later sparse or borrowed-segment workflows can avoid re-uploading bytes.

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:getCasExtents' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts"
}'

Example response:

{
"extents": [
{
"offset": "0",
"length": "512",
"hash": "sha256:5b0f4f5e4f07a65f75f2e89a7216a4f6c2433e6c8f86f6c6c7f97ccf4b64560f",
"casOffset": "0"
},
{
"offset": "512",
"length": "772",
"hash": "sha256:8ce4f2d3a39d1e0b688af9a42f27de6ec2cc1fb8bf4cdd17193cde918f112390",
"casOffset": "0"
}
]
}

Use it for:

  • advanced sync clients
  • sparse patch planning
  • borrowed segment reuse

21. Set, list, get, and remove xattrs

These routes manage extended attributes attached to a path.

Set an xattr

Route:

  • POST /v1/sessions/{sessionId}:setXattr

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:setXattr' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"name": "user.review.status",
"value": "bmVlZHMtYXR0ZW50aW9u"
}'

Decoded value:

  • bmVlZHMtYXR0ZW50aW9u -> needs-attention

Response:

{}

List xattrs

Route:

  • POST /v1/sessions/{sessionId}:listXattrs

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:listXattrs' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts"
}'

Response:

{
"names": [
"user.review.status",
"user.owner"
]
}

Get one xattr

Route:

  • POST /v1/sessions/{sessionId}:getXattr

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:getXattr' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"name": "user.review.status"
}'

Response:

{
"value": "bmVlZHMtYXR0ZW50aW9u"
}

Remove an xattr

Route:

  • POST /v1/sessions/{sessionId}:removeXattr

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:removeXattr' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"name": "user.review.status"
}'

Response:

{}

Use xattrs when you need path-local metadata that is more file-system-like than annotations, but should still stay out of normal file content.

22. Inspect and set POSIX-style locks

These routes expose lower-level POSIX locking semantics.

Inspect a possible conflicting lock

Route:

  • POST /v1/sessions/{sessionId}:getPosixLock

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:getPosixLock' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"kind": "POSIX_LOCK_KIND_EXCLUSIVE",
"range": {
"start": "0",
"length": "0"
},
"owner": {
"ownerId": "1001",
"pid": 4242,
"clientId": "7"
},
"isFlock": false
}'

Example response with a conflict:

{
"hasConflict": true,
"conflict": {
"kind": "POSIX_LOCK_KIND_EXCLUSIVE",
"range": {
"start": "0",
"length": "0"
},
"owner": {
"ownerId": "1002",
"pid": 4310,
"clientId": "8"
},
"isFlock": false
}
}

Set a non-blocking POSIX lock

Route:

  • POST /v1/sessions/{sessionId}:setPosixLock

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:setPosixLock' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"kind": "POSIX_LOCK_KIND_EXCLUSIVE",
"range": {
"start": "0",
"length": "0"
},
"owner": {
"ownerId": "1001",
"pid": 4242,
"clientId": "7"
}
}'

Response:

{}

Set a blocking POSIX lock

Route:

  • POST /v1/sessions/{sessionId}:setPosixLockBlocking

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:setPosixLockBlocking' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"kind": "POSIX_LOCK_KIND_SHARED",
"range": {
"start": "0",
"length": "0"
},
"owner": {
"ownerId": "1001",
"pid": 4242,
"clientId": "7"
}
}'

Response:

{}

Whole-file flock

Route:

  • POST /v1/sessions/{sessionId}:flock

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:flock' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"mode": "POSIX_FLOCK_MODE_EXCLUSIVE",
"owner": {
"ownerId": "1001",
"pid": 4242,
"clientId": "7"
},
"blocking": false
}'

Response:

{}

Unlock

Route:

  • POST /v1/sessions/{sessionId}:funlock

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd:funlock' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"owner": {
"ownerId": "1001",
"pid": 4242,
"clientId": "7"
},
"range": {
"start": "0",
"length": "0"
},
"isFlock": false
}'

Response:

{}

Use these routes when your client needs low-level POSIX-compatible lock behavior. For higher-level collaboration between product users, prefer the file lock routes below.

23. Use collaboration file locks

These routes are higher-level than POSIX locks and are usually the better fit for product workflows involving editors, review tools, and multiple human or agent actors.

Acquire a file lock

Route:

  • POST /v1/sessions/{sessionId}/files/lock

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/files/lock' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"timeoutSeconds": 300
}'

Response:

{
"lockId": "lock_01HRJ8KXQMA75VPYF1E56DHQ0N"
}

Renew a file lock

Route:

  • POST /v1/sessions/{sessionId}/files/renew-lock

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/files/renew-lock' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"lockId": "lock_01HRJ8KXQMA75VPYF1E56DHQ0N",
"timeoutSeconds": 300
}'

Response:

{
"lockId": "lock_01HRJ8KXQMA75VPYF1E56DHQ0N",
"remainingTtlSeconds": "300"
}

List active locks

Route:

  • POST /v1/sessions/{sessionId}/files/list-locks

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/files/list-locks' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"pathPrefix": "/src"
}'

Response:

{
"locks": [
{
"path": "/src/main.ts",
"lockId": "lock_01HRJ8KXQMA75VPYF1E56DHQ0N",
"ownerId": "alice",
"remainingTtlSeconds": "287"
}
]
}

Unlock

Route:

  • POST /v1/sessions/{sessionId}/files/unlock

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/files/unlock' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"lockId": "lock_01HRJ8KXQMA75VPYF1E56DHQ0N"
}'

Response:

{}

Force unlock

Route:

  • POST /v1/sessions/{sessionId}/files/force-unlock

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/files/force-unlock' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"path": "/src/main.ts",
"reason": "client crashed during review handoff"
}'

Response:

{
"lockId": "lock_01HRJ8KXQMA75VPYF1E56DHQ0N",
"ownerId": "alice"
}

24. Get a session health snapshot

Typical route:

  • GET /v1/sessions/{sessionId}/health

Example request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/health' \
-H 'Authorization: Bearer <token>'

Example response:

{
"health": {
"status": "STATUS_PASSING",
"testsPassed": 128,
"testsFailed": 0,
"testsSkipped": 3,
"lastUpdatedAt": "2026-03-11T08:12:55Z"
}
}

Use this route for:

  • session status badges
  • support dashboards
  • “last known health” views in a review UI

25. Report test results and update health

Primary route:

  • POST /v1/sessions/{sessionId}/health/report

The contract also exposes the alias:

  • POST /v1/sessions/{sessionId}/health:testResults

Example request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/health/report' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"reportFormat": "junit-xml/v1",
"reportContent": "PHRlc3RzdWl0ZSB0ZXN0cz0iMiIgZmFpbHVyZXM9IjAiLz4="
}'

Decoded report content:

  • PHRlc3RzdWl0ZSB0ZXN0cz0iMiIgZmFpbHVyZXM9IjAiLz4= -> <testsuite tests="2" failures="0"/>

Example response:

{
"newHealthStatus": {
"status": "STATUS_PASSING",
"testsPassed": 2,
"testsFailed": 0,
"testsSkipped": 0,
"lastUpdatedAt": "2026-03-11T08:12:55Z"
}
}

Use this route when an agent or runner needs to report health back into the shared session state.

26. Get session analytics

Typical route:

  • GET /v1/sessions/{sessionId}/analytics

Example request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/analytics' \
-H 'Authorization: Bearer <token>'

Example response:

{
"sessionId": "sess_1234abcd",
"timeToFirstWriteMs": "4820",
"totalFileMutations": "37",
"checkpointCount": "3",
"restoreCount": "1",
"lastReportedHealth": {
"status": "STATUS_PASSING",
"testsPassed": 128,
"testsFailed": 0,
"testsSkipped": 3,
"lastUpdatedAt": "2026-03-11T08:12:55Z"
},
"completionTime": "2026-03-11T08:15:31Z"
}

Use this route for:

  • workspace analytics views
  • efficiency reporting
  • post-run summaries

27. Get tenant usage

Typical route:

  • GET /v1/usage

Example request:

curl 'https://api.aetherfs.io/v1/usage?windowStart=2026-03-01T00:00:00Z&windowEnd=2026-03-31T23:59:59Z&granularity=day' \
-H 'Authorization: Bearer <token>'

Example response:

{
"tenantId": "tenant_acme",
"windowStart": "2026-03-01T00:00:00Z",
"windowEnd": "2026-03-31T23:59:59Z",
"granularity": "day",
"aggregationVersion": 3,
"sessionHours": 184.5,
"storageGbMonth": 92.75,
"egressGb": 18.2
}

Use this route for account visibility, billing-style reporting, and tenant-level dashboards.

28. Create, list, and inspect sources

Sources are reusable baselines for new sessions.

Create a source

Route:

  • POST /v1/sources

Request:

curl -X POST 'https://api.aetherfs.io/v1/sources' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"displayName": "webapp-main",
"gitRemote": {
"remoteUrl": "https://github.com/acme/webapp.git",
"defaultRef": "main",
"credentialsSecretId": "sec_git_pat_prod"
},
"labels": {
"team": "web",
"env": "prod"
}
}'

Response:

{
"sourceId": "src_webapp",
"displayName": "webapp-main",
"kind": "UNDERLAY_SOURCE_KIND_GIT_REMOTE",
"status": "UNDERLAY_SOURCE_STATUS_READY",
"latestRevisionId": "rev_2026_03_11_main",
"createdAt": "2026-03-11T08:20:10Z",
"updatedAt": "2026-03-11T08:20:10Z",
"createdByPrincipal": "alice@example.com",
"labels": {
"team": "web",
"env": "prod"
}
}

List sources

Route:

  • GET /v1/sources

Request:

curl 'https://api.aetherfs.io/v1/sources?filter=team%3Dweb&pageSize=20' \
-H 'Authorization: Bearer <token>'

Response:

{
"sources": [
{
"sourceId": "src_webapp",
"displayName": "webapp-main",
"kind": "UNDERLAY_SOURCE_KIND_GIT_REMOTE",
"status": "UNDERLAY_SOURCE_STATUS_READY",
"latestRevisionId": "rev_2026_03_11_main",
"createdAt": "2026-03-11T08:20:10Z",
"updatedAt": "2026-03-11T08:20:10Z",
"createdByPrincipal": "alice@example.com",
"labels": {
"team": "web",
"env": "prod"
}
}
],
"nextPageToken": ""
}

Get one source

Route:

  • GET /v1/sources/{sourceId}

Request:

curl 'https://api.aetherfs.io/v1/sources/src_webapp' \
-H 'Authorization: Bearer <token>'

Response:

{
"sourceId": "src_webapp",
"displayName": "webapp-main",
"kind": "UNDERLAY_SOURCE_KIND_GIT_REMOTE",
"status": "UNDERLAY_SOURCE_STATUS_READY",
"latestRevisionId": "rev_2026_03_11_main",
"createdAt": "2026-03-11T08:20:10Z",
"updatedAt": "2026-03-11T08:20:10Z",
"createdByPrincipal": "alice@example.com",
"labels": {
"team": "web",
"env": "prod"
}
}

29. Start import, poll job status, refresh a source, and promote a session

These routes all return the import-job shape, so clients should treat them as asynchronous flows.

Start a source import

Route:

  • POST /v1/sources/{sourceId}/startImport

Request:

curl -X POST 'https://api.aetherfs.io/v1/sources/src_webapp/startImport' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"git": {
"gitRef": "refs/heads/main"
}
}'

Response:

{
"jobId": "job_01HRJD8JAZM4H1JY70T6QW5C0V",
"sourceId": "src_webapp",
"state": "UNDERLAY_IMPORT_JOB_STATE_QUEUED",
"revisionId": "",
"createdAt": "2026-03-11T08:30:04Z",
"startedAt": "",
"completedAt": "",
"errorCode": "",
"errorMessage": "",
"bytesProcessed": "0",
"filesProcessed": "0"
}

Poll the import job

Route:

  • GET /v1/sources/{sourceId}/jobs/{jobId}

Request:

curl 'https://api.aetherfs.io/v1/sources/src_webapp/jobs/job_01HRJD8JAZM4H1JY70T6QW5C0V' \
-H 'Authorization: Bearer <token>'

Response:

{
"jobId": "job_01HRJD8JAZM4H1JY70T6QW5C0V",
"sourceId": "src_webapp",
"state": "UNDERLAY_IMPORT_JOB_STATE_SUCCEEDED",
"revisionId": "rev_2026_03_11_main",
"createdAt": "2026-03-11T08:30:04Z",
"startedAt": "2026-03-11T08:30:07Z",
"completedAt": "2026-03-11T08:31:19Z",
"errorCode": "",
"errorMessage": "",
"bytesProcessed": "14829203",
"filesProcessed": "4211"
}

Refresh a source

Route:

  • POST /v1/sources/{sourceId}/refresh

Request:

curl -X POST 'https://api.aetherfs.io/v1/sources/src_webapp/refresh' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"gitRef": "refs/heads/release/2026.03"
}'

Response:

{
"jobId": "job_01HRJDCJ2GG7CH80X3Q5RP83V1",
"sourceId": "src_webapp",
"state": "UNDERLAY_IMPORT_JOB_STATE_QUEUED",
"revisionId": "",
"createdAt": "2026-03-11T08:34:41Z",
"startedAt": "",
"completedAt": "",
"errorCode": "",
"errorMessage": "",
"bytesProcessed": "0",
"filesProcessed": "0"
}

Promote a session into a source

Route:

  • POST /v1/sources/{sourceId}/promoteSession

Request:

curl -X POST 'https://api.aetherfs.io/v1/sources/src_webapp/promoteSession' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"sessionId": "sess_1234abcd",
"revisionLabel": "release-candidate-7"
}'

Response:

{
"jobId": "job_01HRJDHQK3QSWCZJQ12XCEEFK8",
"sourceId": "src_webapp",
"state": "UNDERLAY_IMPORT_JOB_STATE_QUEUED",
"revisionId": "",
"createdAt": "2026-03-11T08:39:17Z",
"startedAt": "",
"completedAt": "",
"errorCode": "",
"errorMessage": "",
"bytesProcessed": "0",
"filesProcessed": "0"
}

30. Read the session event log

Typical route:

  • GET /v1/sessions/{sessionId}/events

Use this route when you want an immutable, paginated audit trail of session changes rather than a live message stream.

Example request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/events?startTime=2026-03-11T08:00:00Z&endTime=2026-03-11T09:00:00Z&pageSize=2' \
-H 'Authorization: Bearer <token>'

Example response:

{
"events": [
{
"eventId": "evt_01HRJA1KR9T0S8A4P1B5VVN5ER",
"timestamp": "2026-03-11T08:12:11Z",
"eventType": "v1.FileSystemService.Rename",
"details": {
"sourcePath": "/src/legacy/config.ts",
"destinationPath": "/src/config/index.ts"
}
},
{
"eventId": "evt_01HRJA235Q9B0W7R5E2KJ4N55V",
"timestamp": "2026-03-11T08:18:04Z",
"eventType": "v1.AnnotationService.AddAnnotation",
"details": {
"filePath": "/src/main.ts",
"annotationId": "ann_01HRJ5Q8D3RMY8H7RWX7Y1AVRJ"
}
}
],
"nextPageToken": "eyJvZmZzZXQiOjJ9"
}

Use the event log for:

  • audit trails
  • support tooling
  • reconstructing what changed over time
  • paginated historical views

Use the bus for live activity. Use the event log for durable history.

31. List, set, get, and delete secrets

The secrets surface is session-scoped for authorization and visibility, but the values themselves are not ordinary filesystem content.

Important boundary from the public contract:

  • retrieved secret values should never be written into the session overlay as ordinary files
  • secret values should be redacted from logs

List visible secrets

Route:

  • GET /v1/sessions/{sessionId}/secrets

Request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/secrets?scope=SECRET_SCOPE_SESSION' \
-H 'Authorization: Bearer <token>'

Response:

{
"secrets": [
{
"secretName": "NPM_TOKEN",
"scope": "SECRET_SCOPE_SESSION"
},
{
"secretName": "OPENAI_API_KEY",
"scope": "SECRET_SCOPE_SESSION"
}
]
}

Set a secret

Route:

  • PUT /v1/sessions/{sessionId}/secrets/{secretName}

Request:

curl -X PUT 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/secrets/NPM_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"secretValue": "npm_xxxxxxxxxxxxxxxxx",
"scope": "SECRET_SCOPE_SESSION"
}'

Response:

{
"secretName": "NPM_TOKEN",
"scope": "SECRET_SCOPE_SESSION"
}

Get a secret

Route:

  • GET /v1/sessions/{sessionId}/secrets/{secretName}

Request:

curl 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/secrets/NPM_TOKEN' \
-H 'Authorization: Bearer <token>'

Response:

{
"secretName": "NPM_TOKEN",
"secretValue": "npm_xxxxxxxxxxxxxxxxx"
}

Delete a secret

Route:

  • DELETE /v1/sessions/{sessionId}/secrets/{secretName}

Request:

curl -X DELETE 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/secrets/NPM_TOKEN?scope=SECRET_SCOPE_SESSION' \
-H 'Authorization: Bearer <token>'

Response:

{
"secretName": "NPM_TOKEN",
"scope": "SECRET_SCOPE_SESSION"
}

Use secrets for:

  • credentials
  • API tokens
  • connection strings
  • deployment-sensitive inputs that should not live in the working tree

Do not use secrets for:

  • ordinary config that can safely live in source
  • review notes
  • cache entries
  • workflow messages

32. Approval edge cases

The success paths for approval request, grant, and deny are already covered earlier. The cases below are the ones client authors usually miss.

32.1 Granting an approval that has already been resolved

If a reviewer tries to grant an approval that has already been denied or granted, the route still returns the public generic error envelope on failure.

Route:

  • POST /v1/sessions/{sessionId}/approvals/{approvalId}/grant

Representative request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/approvals/appr_01HRJ62B2S2H12XATB9TQX6W7R/grant' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{}'

Representative error response:

{
"code": 9,
"message": "approval appr_01HRJ62B2S2H12XATB9TQX6W7R is no longer pending",
"details": []
}

This example is representative of the public google.rpc.Status failure shape. The exact code and message text may vary by deployment and implementation details.

Client guidance:

  • treat approval actions as non-idempotent workflow transitions
  • refresh session and review state after any failure
  • do not assume you can retry grant blindly

32.2 Denying an approval with reviewer feedback

The deny body is intentionally small. Use it to return actionable feedback rather than a long audit trail.

Route:

  • POST /v1/sessions/{sessionId}/approvals/{approvalId}/deny

Request:

curl -X POST 'https://api.aetherfs.io/v1/sessions/sess_1234abcd/approvals/appr_01HRJ62B2S2H12XATB9TQX6W7R/deny' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"feedback": "Split the generated changes into smaller checkpoints and rerun tests before requesting approval again."
}'

Response:

{}

Client guidance:

  • preserve the feedback for the user or agent that requested approval
  • expect the session to return to an active state rather than remain blocked forever
  • follow denial with fresh annotations, a new summary, or a new approval request only after the underlying issue is fixed

32.3 Unknown approval ID

If the approval ID is wrong or no longer visible in the current auth context, the failure still uses the same generic public error envelope.

Representative error response:

{
"code": 5,
"message": "approval appr_missing was not found for session sess_1234abcd",
"details": []
}

Client guidance:

  • do not synthesize approval IDs client-side
  • store the approvalId returned by request-approval
  • refresh the session state before presenting retry controls

33. Standard error shape

Unexpected errors follow the google.rpc.Status shape in the public contract.

Example:

{
"code": 7,
"message": "permission denied for session sess_1234abcd",
"details": []
}

Treat this as the generic envelope for authorization, validation, not-found, and other API failures unless a route documents something more specific.

Use these examples as a starting point, then combine them into workflows such as:

  1. create session
  2. read manifest
  3. annotate or publish progress
  4. request approval
  5. commit after approval

See also: