# 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.