# Files and folders Object Storage () keeps any file (PDFs, images, exports, raw data) in folders, with every earlier version. Use `nexus.folders` for folders and their files, and `nexus.files` to find single files anywhere. File bytes travel directly between your machine and storage through short-lived links, never through the API. You only see what your access reaches. ## Before you start [#before-you-start] - Folder access decides what you can do: **read**, **write** or **manage**. Owners and admins reach everything. - Every example assumes `nexus = Nexus.connect()`. ## Open a folder [#open-a-folder] Folders are addressed by path, the way you see them in Object Storage: ```python from klarety import NotFoundError try: reports = nexus.folders.at("reports/q3") except NotFoundError: reports = nexus.folders.create("reports").mkdir("q3") ``` - `nexus.folders.list()` lists the folders you start from. - `nexus.folders.create(name)` makes a top-level folder that you manage. - `folder.mkdir(name)` makes a folder inside another and returns it. - `folder.ensure(["2026/11", "2026/12"])` makes any missing folders along each path and returns each path's folder ID. - `nexus.folders.home(project_id)` is a project's own folder. ## Upload a file [#upload-a-file] ```python title="upload_report.py" from klarety import Nexus, RefusedError with Nexus.connect() as nexus: folder = nexus.folders.at("reports/q3") try: saved = folder.upload("q3-summary.pdf") except RefusedError as refused: raise SystemExit(f"Upload refused: {refused}") print(saved["file_id"], saved["version"]) ``` `upload()` accepts a path or raw `bytes`, and an optional `name` for the file in the folder. It waits until the file is recorded (up to a minute) and returns its details. Reusing a name keeps the old file as an earlier version. For a burst of uploads, pass `wait=False`: the upload is only accepted, and the file appears within seconds. ## List, find and download [#list-find-and-download] ```python for entry in folder.list(): # folders first, then files print(entry["kind"], entry["name"]) for found in nexus.files.find("invoice"): print(found["name"]) nexus.files.download(file_id, "local-copy.pdf") # streams to disk data = nexus.files.open(file_id) # whole file in memory folder.download("./q3") # every file directly in the folder ``` `find()` returns newest first, page by page, and can filter by `folder`, `project`, `uploaded_by`, `type`, `extension` and upload or capture dates. For a large file, use `download()` rather than `open()`. ### Share a link [#share-a-link] ```python url = nexus.files.link(file_id, expires_in_seconds=3600, preview=True) ``` A link lasts five minutes unless you ask for longer, up to one hour. `preview=True` opens the file in a browser instead of downloading it. ## Versions, moves and the trash [#versions-moves-and-the-trash] ```python nexus.files.versions(file_id) nexus.files.restore_version(file_id, 2) nexus.files.update(file_id, name="q3-final.pdf", tags=["finance"], description="Board pack") nexus.files.move(file_id, nexus.folders.at("archive")) nexus.files.delete(file_id) # restorable for your plan's undelete window nexus.files.restore(file_id) ``` Folders work the same way: `folder.rename()`, `folder.move()`, `folder.delete()` (with everything inside) and `folder.restore()`. ## Give access to a folder [#give-access-to-a-folder] ```python print(folder.access()) # who reaches it, and which folder that comes from folder.grant("person", user_id, "read") folder.revoke("person", user_id) ``` `grant()` takes the kind of principal (`person`, `project` or `org` for the whole workspace), its ID and a level: `read`, `write` or `manage`. A folder follows its parent's access until you call `folder.own_access()`, and `folder.follow_parent()` goes back. To let a project reach a folder, link it: `nexus.folders.link(project_id, folder)` (read only unless you pass `level="write"`). `nexus.folders.unlink()` removes the link and keeps the folder. ## Turn files into a table [#turn-files-into-a-table] ```python run = folder.load(into="daily_exports", mode="append") # every file in the folder, now watch = folder.watch(into="daily_exports", mode="append") # each new file, from now on folder.unwatch(watch["watch_id"]) ``` A watch loads each new file in the folder into the table once. To stream events from your app, `folder.send_events(events)` writes them as JSON Lines files for the watch to load. See [Ingest data](/docs/developers/ingest) for modes and keys. ## Follow what changed [#follow-what-changed] ```python for event in nexus.files.activity(folder=folder.folder_id): print(event) events, cursor = nexus.files.changes() # every change, oldest first events, cursor = nexus.files.changes(cursor) # only what changed since ``` Keep the cursor between runs to process each change once. ## What can go wrong [#what-can-go-wrong] | Exception | Cause | What to do | | --- | --- | --- | | `NotFoundError` | The path does not exist, or you cannot reach it. | Check the path as Object Storage shows it. | | `RefusedError` | You lack the access level, or the file is larger than your plan allows. | Ask for access, or connect the bucket it lives in. | | `ConflictError` | The folder or file changed while you worked on it. | Read it again, then retry. | ## Next steps [#next-steps] - [Ingest data](/docs/developers/ingest) to load files into tables. - [Connections](/docs/developers/connections) to read files that stay in your own bucket. - [Limits and rate limits](/docs/developers/limits) for upload size and undelete window.