# API 调用与筛选

## 实例方法

| 方法 | 公共 GET 端点 | 返回值 |
|---|---|---|
| `getTypes()` | `/types` | `hdris/textures/models` 数组 |
| `getAssets(params)` | `/assets` | 资产 ID → 元数据表 |
| `getInfo(id)` | `/info/{id}` | 单个资产元数据 |
| `getFiles(id)` | `/files/{id}` | 文件变体树 |
| `getAuthor(id)` | `/author/{id}` | 作者数据 |
| `getCategories(assetType="all",within)` | `/categories/{type}` | 旧分类计数或类型计数 |
| `getTaxonomy(assetType)` | `/taxonomy/{type}` 或 `/taxonomy` | 分类树与属性模式 |
| `search(text,params)` | `/search` | 排名结果及 total 等信息 |

覆盖核对时的公开 OpenAPI 文档全部 9 个路径。不包装后台管理、鉴权或未文档化端点。

结果保留服务端 JSON 结构，不固定所有字段。元数据中的 `type` 是数字：0 HDRI、1 材质、2 模型；查询参数的 `type/t` 则是字符串。

## 分类和属性筛选

```aardio
import web.rest.polyHaven;
var ph = web.rest.polyHaven("My3DTool/1.0");

var schema,err = ph.getTaxonomy("models");
if(!schema) { ph.close();return null,err; }
print(schema.categories,schema.attributes);

// 属性名、枚举值以 taxonomy 的实际响应为准。
var assets,err = ph.getAssets({
    type="models";
    material=["wood","metal"]
});
ph.close();
if(!assets) return null,err;
for id,asset in assets { print(id,asset.name); }
```

- `category` 是单一路径分类，支持官方 canonical path 或 slug path；父类包含子类。
- `categories` 是旧分类数组/逗号分隔字符串，不等同于 `category`。
- `getAssets`、`search` 的数组值会转换为 CSV；布尔值转为 `true/false` 字符串。数值 0 保留，输入参数表不变。
- 不替调用方构造 taxonomy 中不存在的属性；未知值产生的 HTTP 400 会原样返回。

## 搜索

```aardio
import web.rest.polyHaven;
var ph = web.rest.polyHaven("My3DTool/1.0");
var hits,err = ph.search("mossy rock",{
    t="models";
    limit=10;
    min=0.35;
    future=false
});
ph.close();
if(!hits) return null,err;
for i,item in hits.results { print(item.slug,item.score); }
```

搜索词 trim/lower 以复用服务端等价查询缓存。`results` 数组顺序是排名；`score` 不一定能解释混合搜索顺序，不重新按 score 排序。`total` 可能大于 `#results`。通常保持 `future=false`，不要假定提前展示的资产已经可公开下载。

库不自动猜测搜索故障后的候选资产。HTTP 429/503 时由调用方决定等待或使用已有元数据做本地检索。

## 原生 web.rest 接口与错误

```aardio
import web.rest.polyHaven;
var ph = web.rest.polyHaven("My3DTool/1.0");
ph.setTimeouts(15000,30000,30000);

// 保留原生接口。所有路径片段由 inet.url.encode 编码。
var api = ph.api();
var info,err,code = api.info["ceramic_vase_03"].get();
if(!info) {
    print(err,code,ph.lastStatusCode);
    print(ph.lastResponseError());
}
ph.close();
```

网络失败返回 `null,err,code`；封装自己的输入检查失败通常返回 `null,err`。客户端 `lastStatusCode`、`lastResponseString()`、`lastResponseError()` 与 `web.rest` 一致。输入检查没有发起请求时，HTTP 状态仍属于上一次请求。

命名方法会处理 CSV 参数，原生 `api().xxx.get(params)` 不额外处理这些参数。遇到限流可读取服务端 `Retry-After`：应在请求前按 `web.rest` 规则注册所需响应头，例如请求前设置 `ph.responseHeaders={["Retry-After"]=true}`，请求后读取 `ph.responseHeaders["Retry-After"]`（只有字符串才是实际响应值）。每次请求前重新初始化，避免沿用上一次的值；没有响应头时不要杜撰等待时间。
