OneLake & Fabric APIs — what we need¶
Two API families together give us everything we need:
- Fabric REST API (
https://api.fabric.microsoft.com) — for discovery: workspaces, items, metadata. - OneLake DFS API (
https://onelake.dfs.fabric.microsoft.com) — for file I/O, ADLS Gen2–compatible.
The two APIs require different token audiences — a single audience does not cover both:
- OneLake DFS uses
https://storage.azure.com/(OneLakeScopes). - Fabric REST uses the Power BI Service audience
https://analysis.windows.net/powerbi/api(FabricScopes);api.fabric.microsoft.comaccepts it. Astorage.azure.comtoken returns 401 InvalidToken on Fabric REST.
OFEM acquires the OneLake token interactively at sign-in and the Fabric token silently from the same refresh token. See docs/auth.md "Two-audience scope model" and Packages/OfemKit/Sources/OfemKit/Auth/TokenScope.swift.
URI shapes¶
Named URI (human-friendly, but sensitive to renames and special characters)¶
GUID URI (immutable; recommended for our internal paths)¶
ABFS style (Hadoop-tool compatible, not what we call directly)¶
Alternate hostnames¶
https://api.onelake.fabric.microsoft.com— generic FQDN (no.dfs/.blobsubstring; may cause compat issues with some Azure SDKs, but is fine for our own client).https://<region>-api.onelake.fabric.microsoft.com— regional variant.https://<wsid>.z<xy>.dfs.fabric.microsoft.com— workspace FQDN for private endpoints (only relevant when the user is in such an environment).
Decision: default to onelake.dfs.fabric.microsoft.com; expose the private-link FQDN as an advanced configuration option.
Discovery — Fabric REST API¶
List workspaces¶
Response includes id (GUID), displayName, type, capacityId, optionally domainId per workspace.
List items inside a workspace¶
Returns id, displayName, type (e.g. Lakehouse, Warehouse, KQLDatabase, MirroredDatabase, SemanticModel, Notebook, …), description, workspaceId.
Type-specific item endpoints (extra metadata)¶
Certain item types have a type-specific endpoint that returns more info:
→ returns OneLake paths to /Files and /Tables, plus the SQL analytics endpoint connection string.
Similar endpoints exist for eventhouses, kqlDatabases, warehouses, mirroredDatabases, …
OFEM uses the generic Get/List Item endpoints and does not special-case every item type.
Catalog search (optional)¶
Powerful for "find all items matching X", but not critical for file browsing.
Shortcuts¶
Returns, per shortcut, the path within the item and the target type (OneLake, ADLS Gen2, Amazon S3, Google Cloud Storage, S3 Compatible, Dataverse, External Data Share). Shortcuts are transparent: they appear as a folder in the DFS-API listing without any special handling.
File I/O — OneLake DFS API (ADLS Gen2–compatible)¶
OneLake speaks the ADLS Gen2 REST API. Mapping:
| ADLS concept | OneLake equivalent |
|---|---|
| Account name | always onelake |
| Container / filesystem name | workspace name or GUID |
| Path | starts at the item, e.g. /mylakehouse.lakehouse/Files/foo.csv |
Operations we need¶
Filesystem (= workspace):
- HEAD / — does the workspace exist (useful for a permission check)
- GET /?resource=filesystem&recursive=false — list top-level items
Path (= folder or file):
- GET /{path}?resource=filesystem&recursive=false&directory={dir} — folder listing
- HEAD /{path} — metadata (size, last-modified, etag, content-type)
- GET /{path} — read file content (with Range header for partial reads)
- PUT /{path}?resource=file — create file
- PATCH /{path}?action=append&position=N — append data
- PATCH /{path}?action=flush&position=N — flush after append
- PUT /{path}?resource=directory — create folder
- DELETE /{path} — delete (folder or file)
- PUT /{path} with x-ms-rename-source header — rename / move
What OneLake does not support via the ADLS API (parity gaps)¶
Important to know for good error handling:
- No ACL operations: headers
x-ms-owner,x-ms-group,x-ms-permissions,x-ms-aclsare ignored;?action=setAccessControl[Recursive]is rejected. Permissions are managed through the Fabric portal. - No access tier or encryption scope: headers ignored.
- Workspace and item folders are protected: you cannot create, rename, or delete them via the DFS API.
- The first level inside an item (e.g.
/MyLakehouse.lakehouse/Filesand/MyLakehouse.lakehouse/Tables) is Fabric-managed and not modifiable. - Page blobs: not supported (
Put Page,Get Page Ranges). - Cross-tenant operations: not possible in a single API call; must go through External Data Sharing or via download-and-upload.
Tables endpoint (not used)¶
Iceberg REST Catalog and Delta protocols for table metadata. Not needed for file browsing; potentially relevant if we ever add table preview.
Throughput considerations¶
- DFS endpoints support parallel reads via
Rangeheaders — good for large files. - For uploads of large files: chunked write through
append+flushis more efficient than a single bigPUT. - Listings are paginated through the
continuationparameter — implement pagination correctly or you will miss files in large folders. - HTTP is an Alamofire
SessionoverURLSession(seePackages/OfemKit/Sources/OfemKit/HTTP/SessionPool.swift), pooled per(alias, scope)so token refresh and connection limits stay scoped to the right account and audience.timeoutIntervalForRequestis 60 s, buttimeoutIntervalForResourceis deliberately.infinity: a resource-level wall-clock cap would kill a multi-GiB download or append stream mid-transfer at modest bandwidth. There is no response-header timeout either. Instead, retries are bounded explicitly byRetryAfterRetrier(honorsRetry-After, but only on 429/503, capped atmaxDelay = 30s) ahead ofJitteredRetryPolicy(full-jitter backoff, on408, 425, 429, 500, 502, 503, 504) — other 5xx statuses (500/502/504) never haveRetry-Afterread, they only get the jittered backoff. Both retriers share a combined budget ofmaxRetries = 5attempts, so a repeatedly-throttled request cannot retry indefinitely; the resource never hangs open on a dead connection, it just isn't killed by a fixed clock.
Error handling we must cover explicitly¶
| Status | Meaning | Reaction |
|---|---|---|
401 |
Token expired or no access | Refresh token; if that fails, re-authenticate |
403 |
Authenticated but no permission on the item, or a paused/suspended Fabric capacity | PauseManager.isPausedCapacityError inspects the response body to tell the two apart; either way it maps to .cannotSynchronize, never .notAuthenticated — a 403 must never trigger re-authentication |
404 |
Path does not exist | Standard; a DELETE that 404s is treated as success (the row is already gone, e.g. a replayed retry of an already-committed delete) — see SyncEngine.delete |
409 |
Conflict (e.g. directory already exists) | On upload-with-overwrite, retry |
412 / If-Match |
Etag conflict | Surface "remote was changed; refresh first" |
429 |
Throttling | Exponential backoff with full jitter, honor Retry-After; capped at 5 combined retries and a 30 s Retry-After ceiling — see "Throughput considerations" above |
503 |
Backend temporarily unavailable | Same retry pattern |
x-ms-rejected-headers in the response |
OneLake ignored a header | Log it, do not fail |