房源接入 API

v2.0 · 2026-09

房源开放接口(商家版)· 房源创建 / 上下架 / 户型 / 房态

面向白名单运营商与持有方商家:自助完成创建、调价、户型维护、逐晚房态、上下架,并接住客户订单(查单 / 确认 / 拒单);C 端目录只展示 online 房源。鉴权复用平台商家开放接口同一套 HMAC-SHA256 机制(与家政开放接口同签名算法、同密钥体系)。

RESTful · JSON 统一 HMAC-SHA256 鉴权 14 个接口 · 全部 POST 房态日历 · 逐晚库存 owner 隔离 · 越权不可见

1 接入概览

房源开放接口是平台商家开放接口的房源分支:路径前缀 /api/juzhu/housing/vendor/*,全部为 POST,请求/响应均为 JSON。鉴权、密钥、错误码风格与家政开放接口(/api/juzhu/jiazheng/vendor/*)完全一致,一套密钥两个业务共用。

  • 提交即生效:房源与户型实时可查,逐晚房态即时更新;客户订单占用的晚自动锁定,任何接口都不可改。
  • 数据归属:房源创建时自动绑定签约商家,所有读写按归属隔离——查询、更新、上下架只能命中自己的房源,他人房源一律 404(不泄露存在性)。
  • 上架语义:statusdraft(草稿,默认)/ online(上架)/ offline(下架);C 端目录只出 online。目录有 ≤ 15 秒缓存,上下架最迟 15 秒生效。

接入流程

1开通凭证平台为商家配置接入密钥,线下下发 vendor_id + hmac_key
2沙箱联调用调试台或脚本签名调通 projects/list(只读)。
3创建房源projects/create 建草稿(默认不入 C 端),补户型与价格。
4平台核验房源评级 / 合规信息由平台侧维护,商家可自查 projects/detail
5上架运营projects/status 上架;日常用房态接口做关房/夜价,随用随调。

环境

环境Base说明
联调/演示https://sytest.meizu.life(本页同源,调试台直接可用)演示库,数据可随时清理重建
生产以平台接入单下发的域名为准同一套接口与签名,仅换 Base
同源调用:本页与 API 同域部署,前端直接请求相对路径 /api/juzhu/housing/vendor/* 即可;跨域接入方按上表 Base 拼全路径。

2 HMAC 签名认证(与家政开放接口同机制)

2.1 凭证

每个商家一个 vendor_id + 一把 hmac_key,由平台统一配置并线下下发。hmac_key 等同密码:只放服务端或自己的联调机,不要写进前端代码、不要提交仓库

2.2 签名算法

  1. 取业务参数对象 body,加上 vendor_id剔除 sign 字段。
  2. 扁平化:嵌套对象用 父键.子键 点号展平;null''(空串)的键不参与签名
  3. 值序列化:字符串原样;数字/布尔转字符串(true→'True'false→'False');数组 → ['a', 'b'](单引号、逗号空格分隔)。
  4. 按键名字典序排序,拼成 k1=v1&k2=v2&…& 连接,无转义)。
  5. 追加 timestamp(毫秒时间戳)参与上述串;用 hmac_keyHMAC-SHA256,取 hex 小写sign
  6. 请求体 = 业务参数 + vendor_id + timestamp + signPOST JSON 发送。时间戳与服务器偏差 ±5 分钟 内有效。
// Node 签名示例
const crypto = require('crypto');

function pyStr(v) {
  if (v === true)  return 'True';
  if (v === false) return 'False';
  if (Array.isArray(v)) return '[' + v.map(x => (typeof x === 'string' ? "'" + x + "'" : pyStr(x))).join(', ') + ']';
  return String(v);
}
function flatten(data, prefix) {
  const flat = {};
  for (const [k, v] of Object.entries(data || {})) {
    if (v == null || v === '') continue;          // 空值不参与签名
    const key = prefix ? prefix + '.' + k : k;
    if (v && typeof v === 'object' && !Array.isArray(v)) Object.assign(flat, flatten(v, key));
    else flat[key] = pyStr(v);
  }
  return flat;
}
function sign(secretKey, body) {
  const payload = { ...body };
  delete payload.sign;
  const timestamp = Date.now();
  const flat = flatten(payload);
  flat.timestamp = String(timestamp);
  const stringToSign = Object.keys(flat).sort().map(k => k + '=' + flat[k]).join('&');
  payload.timestamp = timestamp;
  payload.sign = crypto.createHmac('sha256', secretKey).update(stringToSign, 'utf8').digest('hex');
  return payload;                        // 直接作为 POST body
}

const body = sign(HMAC_KEY, { vendor_id: VENDOR_ID });
await fetch(BASE + '/api/juzhu/housing/vendor/projects/list', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body),
});
易错点:① 数组参与签名用单引号形式(tags=['演示', '回归']),与 JSON 序列化不同;② 嵌套对象(如 units 数组里的对象)会被点号展平进签名,直接把最终 body 交给 sign 函数即可,不要手工拼;③ 重试时须重新签名(timestamp 会过期)。

响应统一为 JSON:成功 { "code": 0, "message": "success", … };失败 HTTP 状态码 + { "code": 4xx, "message": "原因" }(见 错误码)。

3 数据字典

字段与平台房源模型一致、即接口契约本身;带 ★ 为商家写入接口接受的字段。

3.1 房源(projects)

房源/项目名城市 id(cities.id);须在该商家开放城市内
字段类型说明
idint平台生成,创建后返回;后续接口用 id 定位房源
namestring★必填
channelstring★ 业务频道:rental 租赁住宿(长租+旅居,监管同口径)/ minsu 民宿民宿 / newhouse 新房 / resale 二手 / trade 卖旧买新;缺省 rental,创建后不可改
city_idint★必填
district_idint★ 行政区 id(districts.id),须属于 city_id
addressstring★ 详细地址;缺省自动拼「城市 · 名称」
slugstringURL 标识;缺省由名称自动生成并保证同频道唯一
cover_imagestring★ 封面图 URL
tagsstring[]★ 标签数组(如 ["保租房","近地铁"]
price_fromint★ 起价,单位元。rental 口径为「元/月」,minsu 为「元/晚」;上架前置条件
sort_order / is_featured / old_house_hint★ 排序权重 / 平台精选(一般由平台设置)/ 卖旧买新提示
contact_phonestring★ 真实联系电话,仅入库用于虚拟号绑定,任何查询接口不回显
owner_vendor_idint数据归属商家(签名商家自动写入,不可改)
statusstringdraft 草稿(默认)/ online 上架 / offline 下架
extobject★ 频道差异属性:insurance(保险标识数组,见 7)+ min_stay_nights(最短连住,见 6)+ stay_bookable(按晚预订开关,见 5.3)
rating_status / rating评级状态与结果(平台侧维护,商家只读)

3.2 户型(units)

所属房源 id户型名(如「两居 68㎡」「整栋 · 6 室」)
字段类型说明
project_idint★必填
namestring★必填
area_sqmnumber★ 面积(㎡)
layout_labelstring★ 户型标签(2室1厅
rent_monthlyint★ 月租(元/月);rental 频道夜价按 月租/30 折算
price_totalint★ 总价(元),newhouse/resale 频道用
promo_priceint★ 优惠价(元)
price_nightint★ 夜价(元/晚),写入 units.ext.price_nightminsu 频道预订按它计价
cancel_policyobject★ 免费取消政策(房型级),写入 units.ext.cancel_policy{enabled, days_before, cutoff_time};免费取消窗口 = 入住日往前推 days_before(0-30,0=入住当天)天的 cutoff_time(HH:mm)时刻,窗口外 / 未开通一律不可取消不可退
tags / amenities / unit_spec / cover_image / sort_order★ 标签 / 配套枚举数组 / 房型说明 / 封面 / 排序

3.3 房态日历(逐晚库存)

字段说明
project_id + unit_id库存归属;unit_id=0 表示项目级(整栋 / 不限房型)
stay_date晚(YYYY-MM-DD,入住当晚)
statusopen 可订(默认,未设置过的晚即可订)/ blocked 关房(商家)/ booked 已订(平台订单写入,接口不可改,取消自动释放)
price_night当晚价格覆盖(元),未覆盖回落默认夜价;户型级 > 项目级 > 默认
验收工具:GET /api/juzhu/projects/{id}/stay-calendar?month=YYYY-MM&unit_id=(公开只读,用于上下架 / 房态验收)返回逐晚三态 + 价格 + 最短连住 + 保险标识,与 C 端日历渲染一致。

4 房源接口

以下示例省略签名三件套(vendor_id / timestamp / sign),实际每个请求体都必须携带(见 §2,或直接用 §7 调试台)。

4.1 房源列表

POST/api/juzhu/housing/vendor/projects/list本商家房源(最多 200 条)
{
  "channel": "rental",      // 可选过滤:channel / status / city_id / keyword
  "status": "online"
}

→ 200
{
  "code": 0, "message": "success",
  "list": [
    {
      "id": 1611, "name": "示例·观山遇见旅居", "channel": "rental",
      "status": "online", "price_from": 2400,
      "min_stay_nights": 15,
      "insurance": ["switch_rental", "property"],
      "insurance_types": [ { "key": "switch_rental", "label": "换租保险", "icon": "🔄" }, /*…*/ ]
    }
  ],
  "total": 1
}

4.2 房源详情

POST/api/juzhu/housing/vendor/projects/detail含户型明细
{ "id": 1611 }
→ 200 { "code": 0, "project": { /* 同 4.1,另含 address/tags/ext 等 */ }, "units": [ /* 户型数组 */ ] }

4.3 创建房源(默认草稿,不入 C 端)

POST/api/juzhu/housing/vendor/projects/create可带 units 一次建全
{
  "name": "观山湖·遇见旅居",
  "channel": "rental",
  "city_id": 4,
  "district_id": 41,
  "address": "观山湖区 · 示例大道 88 号",
  "tags": ["旅居", "近地铁"],
  "price_from": 2400,
  "contact_phone": "13800001234",       // 仅入库(虚拟号绑定),永不回显
  "min_stay_nights": 15,               // 最短连住,写入 ext(见 §5)
  "insurance": ["switch_rental", "property"],   // 保险标识(见 §6)
  "units": [
    { "name": "一居 45㎡", "layout_label": "1室1厅", "area_sqm": 45, "rent_monthly": 2400 }
  ],
  "status": "draft"                      // 缺省 draft;draft/offline 不出 C 端
}

→ 200 { "code": 0, "message": "success", "project": { "id": 1611, "status": "draft", /*…*/ } }

4.4 更新房源

POST/api/juzhu/housing/vendor/projects/update按 id 增量更新,仅传变更字段
{
  "id": 1611,
  "price_from": 2680,
  "tags": ["旅居", "近地铁", "含早"],
  "insurance": ["hotel_cancel", "property"],
  "min_stay_nights": 20,
  "stay_bookable": true
}
→ 200 { "code": 0, "project": { /* 更新后全量 */ } }

不可改:city_idchannelowner_vendor_id;改 name 会同步重生成唯一 slug

4.5 上下架

POST/api/juzhu/housing/vendor/projects/status上架 / 下架 / 转草稿
{ "id": 1611, "status": "online" }     // online 上架 / offline 下架 / draft 转草稿
→ 200 { "code": 0, "message": "success", "id": 1611, "status": "online" }
上架前置检查:必须已设 price_from 且至少有 1 个户型,否则 400。C 端目录有 ≤ 15 秒缓存,上架后最迟 15 秒可见、下架后最迟 15 秒消失。

4.6 新增户型

POST/api/juzhu/housing/vendor/units/create向已有房源追加户型
{ "project_id": 1611, "name": "两居 68㎡", "layout_label": "2室1厅",
  "area_sqm": 68, "rent_monthly": 3200 }
→ 200 { "code": 0, "unit": { "id": 3101, /*…*/ } }

4.7 更新户型(调价 / 取消政策等)

POST/api/juzhu/housing/vendor/units/update按户型 id 增量更新
{ "id": 3101, "rent_monthly": 3380, "price_night": 128,
  "cancel_policy": { "enabled": true, "days_before": 1, "cutoff_time": "18:00" } }
→ 200 { "code": 0, "unit": { /*…*/ } }
  • 取消政策:传入 cancel_policy(非法 days_before/cutoff_time 返回 400);传 null 清除(= 未开通,客户不可取消)。免费取消窗口 = 入住日往前推 days_before 天的 cutoff_time 时刻;客户在窗口内取消自动退款并释放房态。

4.8 删除户型

POST/api/juzhu/housing/vendor/units/delete有关联订单或被占用晚时拒绝
{ "id": 3101 }
→ 200 { "code": 0, "message": "success" }

4.9 房态查询(商家视角)

POST/api/juzhu/housing/vendor/stay-calendar/query逐晚三态 + 占用来源 + 最短连住/保险
{ "project_id": 1611, "unit_id": 0, "month": "2026-12" }
→ 200 {
  "code": 0, "month": "2026-12", "base_price_night": 80,
  "min_stay_nights": 15, "insurance": ["switch_rental", "property"],
  "days": [
    { "date": "2026-12-19", "status": "booked", "price": 80, "source": "booking", "booking_id": 36 },
    { "date": "2026-12-20", "status": "open", "price": 80 }
  ]
}

5 订单履约(商家接单)

客户在 C 端提交预订单后,商家经开放接口完成接单闭环:定时拉取订单 → 确认生效或拒单 → 拒单自动释放房态。订单按签约商家隔离,联系人手机号只回掩码(139****5678)。

POST/api/juzhu/housing/vendor/bookings/list本商家订单(≤200 条)
{
  "status": "pending",        // 可选过滤:status / pay_status / project_id
  "project_id": 1611
}
→ 200 { "code": 0, "list": [ {
  "id": 36, "order_no": "BKG-RENTAL-00036", "project_name": "观山湖·遇见旅居",
  "checkin": "2026-12-19", "checkout": "2027-01-08", "nights": 20,
  "price_total": 1600, "commission_rate": 8.00, "commission_fee": 128.00,
  "status": "pending", "pay_status": null,
  "contact_name": "张**", "contact_phone": "139****5678"
} ] }
  • 佣金(规则 20)commission_rate / commission_fee 为下单时锁定的费率与佣金快照(元),结算按快照执行、商家后续费率调整不追溯;未差异化商家按全局基准(缺省 10%)。
POST/api/juzhu/housing/vendor/bookings/detail按 id 查单(owner 校验)
{ "id": 36 } → 200 { "code": 0, "booking": { /* 同上,单条 */ } }
POST/api/juzhu/housing/vendor/bookings/confirm确认生效
{ "id": 36 } → 200 { "code": 0, "status": "confirmed" }
POST/api/juzhu/housing/vendor/bookings/cancel拒单 / 取消(自动释放房态)
{ "id": 36 }
→ 200 { "code": 0, "status": "cancelled", "pay_status": null }

订单状态与支付口径

字段取值说明
statuspending → confirmed / cancelled确认后订单生效;取消为终态,不可再变更
pay_statusunpaid / paid / refunded 或 空民宿民宿为预付(先付后确认);租赁住宿预订单为空(商家确认后线下收款)
确认前置unpaid 的订单不可确认(400),支付完成后可确认;cancelled 不可再变更
拒单/取消自动释放占用的房态晚;已支付订单 pay_status 标记 refunded

Webhook 事件通知(平台 → 商家)

客户动作触发的订单变更,平台会主动推送签名事件到你为商家配置的通知地址(接入单里提供 URL 即可),不必轮询等单:

event触发时机商家应做什么
booking.created客户提交预订单备房;预付单等待支付到账
booking.paid预付单支付完成(民宿民宿)此时方可确认订单
booking.cancelled客户取消(商家自己拒单不会推送)释放已备房源

推送为 POST JSON,body 即开放接口同款结构:event + vendor_id + order(订单对象,手机号不外传)+ timestamp + sign验签与开放接口同一算法(对去掉 sign/timestamp 的参数扁平排序后 HMAC-SHA256),收到后先验签再处理:

// Node 验签示例(与平台 hmac_auth.cjs 同算法)
app.post('/hooks/booking', (req, res) => {
  const body = req.body;
  const payload = { ...body };
  delete payload.sign;
  const flat = flatten(payload);              // 与请求签名同一套 flatten
  flat.timestamp = String(body.timestamp);
  const expect = crypto.createHmac('sha256', HMAC_KEY)
    .update(Object.keys(flat).sort().map(k => k + '=' + flat[k]).join('&')).digest('hex');
  if (expect !== String(body.sign).toLowerCase()) return res.status(401).end();
  // 处理 body.event / body.order …
  res.status(200).end('ok');                    // 2xx 即视为送达
});
送达语义:非 2xx 或超时(5s)视为失败,按 5s / 30s / 120s 重试 3 次后放弃——Webhook 是「尽力通知」,不担保必达;请以 bookings/list 定时对账兜底(推荐粒度:每分钟拉一次 status='pending')。订单明细里不含客户明文手机号。
完整业务流(商家视角):创建房源(草稿)→ 补价与户型 → 上架 → 房态/夜价日常维护 → 收 Webhook(或拉单对账)→ 确认或拒单 → 入住 / 离店;任一环节失败都会返回明确的 400/404 与原因(见 错误码)。

6 房态日历与短住规则

5.1 批量设置房态 / 夜价

POST/api/juzhu/housing/vendor/stay-calendar/set关房 / 开房 / 夜价覆盖,单次 ≤ 400 晚
{
  "project_id": 1611,
  "unit_id": 0,                        // 0 或缺省 = 项目级(整栋 / 不限房型)
  "dates": ["2026-12-10", "2026-12-11"],
  "status": "blocked",                 // blocked 关房;open 开房
  "price_night": null                  // blocked 可同时设价;open + 价格 = 夜价覆盖;open + null = 恢复默认
}
→ 200 { "code": 0, "affected": 2, "dates": 2 }
  • 已订晚不可改:区间内有 booked(订单占用)返回 400 并列出日期,须先取消订单。
  • 未设置过的晚天然可订;「恢复默认价」= 清除该晚价格覆盖,回落默认夜价。

5.2 最短连住(min_stay_nights)

旅居/短住按连续入住售卖:平台缺省口径为 rental=15 晚、minsu=1 晚,商家可用 ext.min_stay_nights(1–365)按房源覆盖。三处同口径校验:C 端日历选段、下单页、平台下单接口 POST /api/juzhu/booking(不足返回 400「该房源须连住至少 N 晚」)。下单成功即占用这些晚,取消自动释放。

5.3 按晚预订开关(stay_bookable)

房源默认仅 400 电话咨询(C 端不展示价格日历、下单接口拒绝)。projects/create / projects/update"stay_bookable": true 开通后,C 端才展示日历并支持在线下单(catalog / 详情 / 房态日历出参均带 bookable 能力位)。关闭传 false(已生成订单不受影响,可正常确认/取消)。判断由平台服务端唯一执行,tag / 频道不参与

7 保险标识

商家为房源配置的「入住保障」,随 catalog / 详情 / 房态日历下发 C 端展示。枚举由平台统一维护,接口只接受以下 key,未知 key 返回 400:

key名称保障口径
switch_rental换租保险租期到期换租衔接期内的房租损失与搬迁费用
hotel_cancel酒店取消险行程变更按规则退改,未入住部分房费可退
property财产保险入住期间屋内财产与房屋主体意外损坏

配置入口:projects/create / projects/updateinsurance 数组;清空传 []。出参同时给出 insurance(key 数组)与 insurance_types(含 label/icon,可直接渲染)。

8 在线调试台

在本页直接签名并调用真实接口:选择接口 → 填业务参数 JSON(无需自己写 vendor_id/timestamp/sign)→ 发送。签名在你的浏览器本地完成,hmac_key 只留在本机(可选「记住」,存 localStorage,换机不带)。

密钥安全:调试台等同用你的商家身份操作房源。演示环境请使用测试商家密钥;生产密钥不要在公共电脑上使用或勾选「记住」。
stringToSign 预览会显示在这里(联调对签用)

常用模板:上架 {"id":123,"status":"online"} · 关房 {"project_id":123,"dates":["2026-12-10"],"status":"blocked"} · 建房源见 §4.3 示例 · 接单 {"id":36}(confirm/cancel 同参,id 取 bookings/list 返回值)。

等待发送…

9 错误码

HTTPcode含义处置
401缺失签名(sign)或时间戳(timestamp)参数检查签名三件套是否都在 body 里
401请求已过期(±5 分钟窗口)校准服务器时间后重签
401签名校验失败 / vendor_id 密钥未配置核对 hmac_key 与签名算法(数组、空值、嵌套展平)
400name / city_id / project_id 等必填缺失按 message 补齐
400channel 非法 / city_id 不属于商家开放城市 / district_id 不属于城市先用 projects/list 或平台侧确认主数据
400上架被拒:缺 price_from 或无户型补价 + units/create 后重试
400insurance 含未知标识 / min_stay_nights 不在 1–365按 §6 / §5 枚举修正
400房态:以下日期已有预订占用先取消订单再改房态
400无可更新字段 / city_id 不支持修改按 message 调整
400订单未支付不可确认 / 订单已取消不可变更支付走收银台后重试确认;取消为终态
404404订单不存在或不属于该商家(房源 / 户型同理)统一 404,不区分「不存在」与「越权」,防探测
404404未知 vendor 路由核对路径(本页 8 条)
405非 POST 请求全部接口仅支持 POST

10 联调与回归

平台提供端到端回归脚本(Node,走真实 HTTP 签名链路),接入方可用它自检签名实现是否正确:

# 覆盖:创建→上架→C 端可见→保险/连住更新→房态→下架→越权负例→清理
node scripts/housing_vendor_hmac_regression.cjs [base_url]   # 默认 http://127.0.0.1:8766
node scripts/vendor_hmac_regression.cjs                       # 家政开放接口同机制回归

版本:v2.0(2026-09)· 文档与线上实现同步校准。

密钥与凭证:由平台统一配置并线下下发;遗失须走平台重置,不要在多个业务间共享 hmac_key

试点阶段接口字段以本页为准,扩展字段只增不破坏;重大变更会提前在「接入单」里同步。发布节奏随政策与试点进展而定。