diff --git a/.changeset/prefer-asset-ref.md b/.changeset/prefer-asset-ref.md
new file mode 100644
index 0000000..7fb1aaf
--- /dev/null
+++ b/.changeset/prefer-asset-ref.md
@@ -0,0 +1,6 @@
+---
+"@runflow-io/sdk": patch
+"@runflow-io/studio": patch
+---
+
+Docs: `UploadedAsset.ref` (`runflow://assets/{id}`) is now the recommended model input — the API resolves refs to freshly signed URLs at dispatch (shipped backend-side, formerly RUN-418), so refs never expire in your hands. `url` remains the short-TTL signed HTTPS URL for immediate browser use. Examples and JSDoc updated accordingly; no runtime changes.
diff --git a/packages/proxy/package.json b/packages/proxy/package.json
index ec06276..4284f96 100644
--- a/packages/proxy/package.json
+++ b/packages/proxy/package.json
@@ -26,11 +26,7 @@
"require": "./dist/node.cjs"
}
},
- "files": [
- "dist",
- "README.md",
- "LICENSE"
- ],
+ "files": ["dist", "README.md", "LICENSE"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
diff --git a/packages/sdk/README.md b/packages/sdk/README.md
index e30df6b..ad7b7b0 100644
--- a/packages/sdk/README.md
+++ b/packages/sdk/README.md
@@ -39,19 +39,22 @@ The browser SDK never sees your API key — it's injected by
```ts
const asset = await rf.assets.upload(fileInput.files[0]);
await rf.models.run("google/nano-banana-pro/edit", {
- input: { prompt: "remove the price tag", image_urls: [asset.url] },
+ // asset.ref is the stable runflow://assets/{id} reference — dispatch
+ // resolves it to a fresh signed URL server-side, so it never expires.
+ input: { prompt: "remove the price tag", image_urls: [asset.ref] },
});
```
`upload()` runs the platform's presigned flow (create session → PUT the
bytes to storage → confirm), retrying transient failures, and returns
-`{ id, url, ref, ... }`. `url` is a short-TTL **signed HTTPS URL** —
-pass it straight to any model's media inputs, but don't persist it:
-store `id` (or the stable `runflow://assets/{id}` `ref`) and re-mint a
-fresh url with `rf.assets.get(id)` when needed. Works in the browser
-through `@runflow-io/proxy` (the upload + asset-read endpoints are on
-its default allow-list). Raw `Blob`s need `{ filename }`; the cap is
-50 MB.
+`{ id, url, ref, ... }`. **Prefer `ref`** (`runflow://assets/{id}`) for
+model inputs and for anything you store — the API resolves it to a
+freshly signed URL at dispatch, so it never expires. `url` is a
+short-TTL **signed HTTPS URL** for immediate browser use (previews,
+`
`); re-mint one with `rf.assets.get(id)` when needed. Works in
+the browser through `@runflow-io/proxy` (the upload + asset-read
+endpoints are on its default allow-list). Raw `Blob`s need
+`{ filename }`; the cap is 50 MB.
### Pin-based editing
@@ -148,7 +151,8 @@ information for `buildRequest` and the run helpers.
are URL-encoded; `..`/empty segments are rejected.
- `runflow.runs.get(id)` / `runflow.runs.poll(id)` / `runflow.runs.wait(id)`
- `runflow.assets.upload(file, opts?)` — presigned upload (with retry);
- returns a signed https `url` + stable `runflow://` `ref`.
+ returns the stable `runflow://` `ref` (preferred model input) + a
+ short-TTL signed https `url` for browser display.
- `runflow.assets.get(id)` — re-fetch an asset with a freshly signed `url`.
- `runflow.tools.run(tool, args)` / `runflow.tools.dispatch(tool, args)`
- `runflow.health.check()`
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index 068837f..556d2e6 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -21,11 +21,7 @@
"require": "./dist/index.cjs"
}
},
- "files": [
- "dist",
- "README.md",
- "LICENSE"
- ],
+ "files": ["dist", "README.md", "LICENSE"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
diff --git a/packages/sdk/src/assets.ts b/packages/sdk/src/assets.ts
index 91e723e..46b7749 100644
--- a/packages/sdk/src/assets.ts
+++ b/packages/sdk/src/assets.ts
@@ -27,16 +27,18 @@ export interface UploadedAsset {
/** Asset id (uuid). The durable identifier — store THIS, not `url`. */
id: string;
/**
- * Short-TTL signed HTTPS URL for the file. Pass it directly to model
- * inputs (`image_url`, `image_urls`, `mask_url`, …) right away. Do not
- * persist it — it expires; re-mint a fresh one with `rf.assets.get(id)`
- * (allowed through the proxy by default).
+ * Short-TTL signed HTTPS URL for the file — for immediate browser use
+ * (previews, `
`, downloads). For model inputs prefer `ref`. If you
+ * do pass `url` somewhere, use it right away and never persist it — it
+ * expires; re-mint a fresh one with `rf.assets.get(id)`.
*/
url: string;
/**
- * Canonical stable reference (`runflow://assets/{id}`). Accepted today by
- * ComfyUI workflow file inputs; once the API resolves asset refs at model
- * dispatch (RUN-418), prefer this over the expiring `url`.
+ * Canonical stable reference (`runflow://assets/{id}`) — the PREFERRED
+ * model input. Dispatch resolves it to a freshly signed URL server-side,
+ * so it never expires in your hands. Accepted by model media inputs
+ * (`image_url`, `image_urls`, `mask_url`, …) and ComfyUI file inputs
+ * alike. Store this (or `id`).
*/
ref: string;
/** Original filename. */
@@ -154,10 +156,10 @@ function mapAsset(asset: RawAsset): UploadedAsset {
* const rf = new Runflow({ baseUrl: "/api/runflow" });
* const asset = await rf.assets.upload(fileInput.files[0]);
* await rf.models.run("google/nano-banana-pro/edit", {
- * input: { prompt, image_urls: [asset.url] },
+ * input: { prompt, image_urls: [asset.ref] }, // stable ref — never expires
* });
- * // Later, if you stored asset.id and the signed url expired:
- * const fresh = await rf.assets.get(asset.id);
+ * // asset.url is a short-TTL signed https URL for previews/
;
+ * // re-mint one later with rf.assets.get(asset.id) if needed.
* ```
*/
export class AssetsResource {
diff --git a/packages/studio/README.md b/packages/studio/README.md
index 71cd321..63dafa1 100644
--- a/packages/studio/README.md
+++ b/packages/studio/README.md
@@ -190,7 +190,8 @@ mask.syncToDisplay(rect.width, rect.height, devicePixelRatio);
// pointer events → beginStroke / strokeTo / endStroke; then:
const blob = await mask.toMaskBlob(img.naturalWidth, img.naturalHeight);
const asset = await rf.assets.upload(blob, { filename: "mask.png" });
-// → feed asset.url as mask_url to runflow/reference-inpaint etc.
+// → feed asset.ref (stable, never expires) as mask_url to
+// runflow/reference-inpaint etc.
```
A React state reducer for building a custom shell is tracked as a
diff --git a/packages/studio/package.json b/packages/studio/package.json
index 2b1a6b8..30b3899 100644
--- a/packages/studio/package.json
+++ b/packages/studio/package.json
@@ -13,9 +13,7 @@
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
- "sideEffects": [
- "./dist/styles.css"
- ],
+ "sideEffects": ["./dist/styles.css"],
"exports": {
".": {
"types": "./dist/index.d.ts",
@@ -34,11 +32,7 @@
},
"./styles.css": "./dist/styles.css"
},
- "files": [
- "dist",
- "README.md",
- "LICENSE"
- ],
+ "files": ["dist", "README.md", "LICENSE"],
"scripts": {
"build": "tsup && cp src/styles.css dist/styles.css",
"dev": "tsup --watch",