vtex-io-client-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Client Integration & Service Access

客户端集成与服务访问

When this skill applies

本规范适用场景

Use this skill when the main decision is how a VTEX IO backend app should call VTEX services or external APIs through the VTEX IO client system.
  • Creating custom clients under
    node/clients/
  • Choosing between native clients from
    @vtex/api
    or
    @vtex/clients
    and a custom client
  • Registering clients in
    IOClients
    and exposing them through
    ctx.clients
  • Configuring
    InstanceOptions
    such as retries, timeout, headers, or caching
  • Reviewing backend integrations that currently use raw HTTP libraries
Do not use this skill for:
  • deciding the app contract in
    manifest.json
  • structuring
    node/index.ts
    or tuning
    service.json
  • designing GraphQL schema or resolver contracts
  • modeling route authorization or security permissions
  • building storefront or admin frontend integrations
当你需要决策VTEX IO后端应用如何通过VTEX IO客户端系统调用VTEX服务或外部API时,请使用本规范。
  • node/clients/
    目录下创建自定义客户端
  • 选择使用
    @vtex/api
    @vtex/clients
    提供的原生客户端还是自定义客户端
  • IOClients
    中注册客户端并通过
    ctx.clients
    暴露调用能力
  • 配置
    InstanceOptions
    参数,如重试次数、超时时间、请求头、缓存策略
  • 审查当前使用原生HTTP库的后端集成实现
以下场景请勿使用本规范:
  • manifest.json
    中定义应用契约
  • 搭建
    node/index.ts
    结构或调优
    service.json
    配置
  • 设计GraphQL schema或解析器契约
  • 设计路由授权或安全权限逻辑
  • 构建店铺前端或管理后台前端集成

Decision rules

决策规则

  • Prefer native clients from
    @vtex/api
    or
    @vtex/clients
    when they already cover the target VTEX service. Common examples include clients for catalog, checkout, logistics, and OMS. Write a custom client only when no suitable native client or factory exists.
  • Use
    ExternalClient
    primarily for non-VTEX external APIs. Avoid using it for VTEX-hosted endpoints such as
    *.myvtex.com
    or
    *.vtexcommercestable.com.br
    when a native client in
    @vtex/clients
    ,
    JanusClient
    , or another documented higher-level VTEX client is available or more appropriate.
  • Janus is VTEX's Core Commerce API gateway. Use
    JanusClient
    only when you need to call a VTEX Core Commerce API through Janus and no suitable native client from
    @vtex/clients
    already exists.
  • Use
    InfraClient
    only for advanced integrations with VTEX IO infrastructure services under explicit documented guidance. In partner apps, prefer higher-level clients and factories such as
    masterData
    or
    vbase
    instead of extending
    InfraClient
    directly.
  • Register every custom or native client in
    node/clients/index.ts
    through a
    Clients
    class that extends
    IOClients
    .
  • Consume integrations through
    ctx.clients
    , never by instantiating client classes inside middlewares, resolvers, or event handlers.
  • Keep clients focused on transport, request options, endpoint paths, and small response shaping. Keep business rules, authorization decisions, and orchestration outside the client.
  • When building custom clients, always rely on the
    IOContext
    passed by VTEX IO such as
    account
    ,
    workspace
    , and available auth tokens instead of hardcoding account names, workspaces, or environment-specific VTEX URLs.
  • Configure shared
    InstanceOptions
    in the runtime client config, then use client-specific overrides only when an integration has clearly different needs.
  • Use the
    metric
    option on important client calls so integrations can be tracked and monitored at the client layer, not only at the handler layer.
  • Keep error normalization close to the client boundary, but avoid hiding relevant HTTP status codes or transport failures that are important for observability and debugging.
  • When integrating with external services, confirm that the required outbound policies are declared in the app contract, but keep the detailed policy modeling in auth or app-contract skills.
  • In rare migration or legacy scenarios,
    ExternalClient
    may temporarily be used against VTEX-hosted endpoints, but treat this as an exception. The long-term goal should be to move toward native clients or the proper documented VTEX client abstractions so routing, authentication, and observability stay consistent.
Client selection guide:
Client typeUse whenAvoid when
ExternalClient
calling non-VTEX external APIsVTEX-hosted APIs that already have a native client or Janus-based abstraction
JanusClient
calling VTEX Core Commerce APIs not yet wrapped by
@vtex/clients
any VTEX service that already has a native client such as Catalog, Checkout, Logistics, or OMS
InfraClient
implementing advanced infra-style clients only under explicit documented guidancegeneral VTEX or external APIs in partner apps
InstanceOptions heuristics:
  • Start with small, explicit client defaults such as
    retries: 2
    and a request
    timeout
    between
    1000
    and
    3000
    milliseconds.
  • Use small finite retry values such as
    1
    to
    3
    for idempotent operations.
  • Avoid automatic retries on non-idempotent operations unless the upstream API explicitly documents safe idempotency behavior.
  • Do not use high retry counts to hide upstream instability. Surface repeated failures clearly and handle them intentionally in the business layer.
  • Prefer per-client headers and metrics instead of scattering header definitions through handlers.
  • Use memory or disk cache options only when repeated reads justify it and the response can be safely reused.
  • Keep auth setup inside the client constructor or factory configuration, not duplicated across handlers.
  • @vtex/api
    @vtex/clients
    的原生客户端已经覆盖目标VTEX服务时优先使用原生客户端,常见的例子包括商品目录、结算、物流、订单管理系统(OMS)的客户端。仅当没有合适的原生客户端或工厂方法时再编写自定义客户端。
  • ExternalClient
    主要用于非VTEX的外部API调用。如果要对接的是VTEX托管的端点如
    *.myvtex.com
    *.vtexcommercestable.com.br
    ,且有可用的
    @vtex/clients
    原生客户端、
    JanusClient
    或其他官方文档说明的更高层级VTEX客户端时,避免使用
    ExternalClient
  • Janus是VTEX的核心电商API网关。仅当你需要通过Janus调用VTEX核心电商API,且
    @vtex/clients
    没有提供合适的原生客户端时,再使用
    JanusClient
  • 仅在有明确的官方文档指导下对接VTEX IO基础设施服务的高级集成场景中使用
    InfraClient
    。合作伙伴开发的应用中优先使用
    masterData
    vbase
    等更高层级的客户端和工厂方法,不要直接继承
    InfraClient
  • 所有自定义或原生客户端都需要在
    node/clients/index.ts
    中通过继承
    IOClients
    Clients
    类注册。
  • 通过
    ctx.clients
    调用集成能力,绝对不要在中间件、解析器或事件处理函数中直接实例化客户端类。
  • 客户端仅负责传输逻辑、请求配置、端点路径处理和简单的响应格式化,业务规则、授权决策、流程编排逻辑请放在客户端之外实现。
  • 构建自定义客户端时,始终依赖VTEX IO传递的
    IOContext
    参数(如
    account
    workspace
    、可用的鉴权token),不要硬编码账号名称、工作区或环境相关的VTEX URL。
  • 在运行时客户端配置中设置通用的
    InstanceOptions
    ,仅当某个集成有明确的差异化需求时再使用客户端级别的覆盖配置。
  • 重要的客户端调用请添加
    metric
    配置,这样可以在客户端层面对集成进行跟踪监控,而不局限于处理函数层面。
  • 在客户端边界附近做错误标准化处理,但不要隐藏对可观测性和调试有重要作用的HTTP状态码或传输失败信息。
  • 对接外部服务时,请确认应用契约中已经声明了所需的出站策略,但详细的策略建模请放在授权或应用契约相关规范中处理。
  • 在极少数迁移或遗留场景下,可以临时使用
    ExternalClient
    对接VTEX托管的端点,但这属于例外情况。长期目标应该是迁移到原生客户端或官方文档推荐的VTEX客户端抽象,保证路由、鉴权、可观测性逻辑的一致性。
客户端选择指南:
客户端类型适用场景避免场景
ExternalClient
调用非VTEX的外部API已经有原生客户端或基于Janus的抽象层的VTEX托管API
JanusClient
调用还没有被
@vtex/clients
封装的VTEX核心电商API
已经有原生客户端的VTEX服务,如商品目录、结算、物流、OMS
InfraClient
仅在有明确官方文档指导下实现基础设施类的高级客户端合作伙伴应用中对接普通VTEX服务或外部API的场景
InstanceOptions配置参考:
  • 初始使用明确的小数值默认配置,如
    retries: 2
    、请求超时时间设置在1000到3000毫秒之间。
  • 幂等操作使用1到3次的有限小重试次数。
  • 非幂等操作避免自动重试,除非上游API明确文档说明支持安全幂等。
  • 不要用高重试次数掩盖上游服务的不稳定性,应该明确透出重复失败信息,在业务层主动处理。
  • 优先在客户端层面统一配置请求头和监控指标,不要在各个处理函数中零散定义请求头。
  • 仅当有频繁重复读取请求且响应可以安全复用时,再开启内存或磁盘缓存配置。
  • 鉴权配置放在客户端构造函数或工厂配置中实现,不要在各个处理函数中重复编写。

Hard constraints

硬性约束

Constraint: All service-to-service HTTP calls must go through VTEX IO clients

约束:所有服务间HTTP调用必须通过VTEX IO客户端实现

HTTP communication from a VTEX IO backend app MUST go through
@vtex/api
or
@vtex/clients
clients. Do not use raw libraries such as
axios
,
fetch
,
got
, or
node-fetch
for service integrations.
Why this matters
VTEX IO clients provide transport behavior that raw libraries bypass, including authentication context, retries, metrics, caching options, and infrastructure-aware request execution. Raw HTTP calls make integrations harder to observe and easier to misconfigure.
Detection
If you see
axios
,
fetch
,
got
,
node-fetch
, or direct ad hoc HTTP code in a VTEX IO backend service, STOP and replace it with an appropriate VTEX IO client pattern.
Correct
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

export class WeatherClient extends ExternalClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super('https://api.weather.com', context, {
      ...options,
      headers: {
        'X-VTEX-Account': context.account,
        'X-VTEX-Workspace': context.workspace,
        'X-Api-Key': process.env.WEATHER_API_KEY,
        ...options?.headers,
      },
    })
  }

  public getForecast(city: string) {
    return this.http.get(`/v1/forecast/${city}`, {
      metric: 'weather-forecast',
    })
  }
}
Wrong
typescript
import axios from 'axios'

export async function getForecast(city: string) {
  const response = await axios.get(`https://api.weather.com/v1/forecast/${city}`, {
    headers: {
      'X-Api-Key': process.env.WEATHER_API_KEY,
    },
  })

  return response.data
}
VTEX IO后端应用的HTTP通信必须通过
@vtex/api
@vtex/clients
的客户端实现。不要使用
axios
fetch
got
node-fetch
等原生HTTP库做服务集成。
重要性说明
VTEX IO客户端提供了原生HTTP库没有的传输层能力,包括鉴权上下文传递、重试逻辑、监控指标、缓存配置、感知基础设施的请求执行逻辑。原生HTTP调用会降低集成的可观测性,更容易出现配置错误。
检测方式
如果在VTEX IO后端服务中发现
axios
fetch
got
node-fetch
或者直接手写的HTTP代码,请立即停止开发,替换为合适的VTEX IO客户端模式。
正确示例
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

export class WeatherClient extends ExternalClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super('https://api.weather.com', context, {
      ...options,
      headers: {
        'X-VTEX-Account': context.account,
        'X-VTEX-Workspace': context.workspace,
        'X-Api-Key': process.env.WEATHER_API_KEY,
        ...options?.headers,
      },
    })
  }

  public getForecast(city: string) {
    return this.http.get(`/v1/forecast/${city}`, {
      metric: 'weather-forecast',
    })
  }
}
错误示例
typescript
import axios from 'axios'

export async function getForecast(city: string) {
  const response = await axios.get(`https://api.weather.com/v1/forecast/${city}`, {
    headers: {
      'X-Api-Key': process.env.WEATHER_API_KEY,
    },
  })

  return response.data
}

Constraint: Clients must be registered in IOClients and consumed through ctx.clients

约束:客户端必须在IOClients中注册并通过ctx.clients调用

Clients MUST be registered in the
Clients
class that extends
IOClients
, and middlewares, resolvers, or event handlers MUST access them through
ctx.clients
.
Why this matters
The VTEX IO client registry ensures the current request context, options, caching behavior, and instrumentation are applied consistently. Direct instantiation inside handlers bypasses that shared lifecycle and creates fragile integration code.
Detection
If you see
new MyClient(...)
inside a middleware, resolver, or event handler, STOP. Move the client into
node/clients/
, register it in
IOClients
, and consume it through
ctx.clients
.
Correct
typescript
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'

export class Clients extends IOClients {
  public get catalog() {
    return this.getOrSet('catalog', Catalog)
  }
}
typescript
export async function getSku(ctx: Context) {
  const sku = await ctx.clients.catalog.getSkuById(ctx.vtex.route.params.id)
  ctx.body = sku
}
Wrong
typescript
import { Catalog } from '@vtex/clients'

export async function getSku(ctx: Context) {
  const catalog = new Catalog(ctx.vtex, {})
  const sku = await catalog.getSkuById(ctx.vtex.route.params.id)
  ctx.body = sku
}
客户端必须在继承
IOClients
Clients
类中注册,中间件、解析器、事件处理函数必须通过
ctx.clients
访问客户端。
重要性说明
VTEX IO客户端注册机制可以保证当前请求上下文、配置、缓存逻辑、埋点逻辑的一致性。在处理函数中直接实例化客户端会绕过共享生命周期逻辑,生成脆弱的集成代码。
检测方式
如果在中间件、解析器、事件处理函数中发现
new MyClient(...)
代码,请立即停止开发,将客户端迁移到
node/clients/
目录下,在
IOClients
中注册,再通过
ctx.clients
调用。
正确示例
typescript
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'

export class Clients extends IOClients {
  public get catalog() {
    return this.getOrSet('catalog', Catalog)
  }
}
typescript
export async function getSku(ctx: Context) {
  const sku = await ctx.clients.catalog.getSkuById(ctx.vtex.route.params.id)
  ctx.body = sku
}
错误示例
typescript
import { Catalog } from '@vtex/clients'

export async function getSku(ctx: Context) {
  const catalog = new Catalog(ctx.vtex, {})
  const sku = await catalog.getSkuById(ctx.vtex.route.params.id)
  ctx.body = sku
}

Constraint: Choose the narrowest client type that matches the integration boundary

约束:选择最匹配集成边界的最窄范围客户端类型

Each integration MUST use the correct client abstraction for its boundary. Do not default every integration to
ExternalClient
or
JanusClient
when a more specific client type or native package already exists.
Why this matters
The client type communicates intent and shapes how authentication, URLs, and service boundaries are handled. Using the wrong abstraction makes the integration harder to understand and more likely to drift from VTEX IO conventions.
Detection
If the target is a VTEX Core Commerce API, STOP and check whether a native client from
@vtex/clients
or
JanusClient
is more appropriate than
ExternalClient
. If the target is VTEX-hosted, STOP and confirm that there is no more specific documented VTEX client abstraction before defaulting to
ExternalClient
.
Correct
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export class RatesAndBenefitsClient extends JanusClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(context, options)
  }
}
Wrong
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

export class RatesAndBenefitsClient extends ExternalClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(`https://${context.account}.vtexcommercestable.com.br`, context, options)
  }
}
每个集成必须使用符合其边界的正确客户端抽象。当存在更具体的客户端类型或原生包时,不要所有集成都默认使用
ExternalClient
JanusClient
重要性说明
客户端类型可以传递集成意图,定义鉴权、URL、服务边界的处理方式。使用错误的抽象会降低集成的可理解性,更容易偏离VTEX IO的开发规范。
检测方式
如果对接的是VTEX核心电商API,请先检查
@vtex/clients
的原生客户端或
JanusClient
是否比
ExternalClient
更合适。如果对接的是VTEX托管的服务,请先确认没有更具体的官方VTEX客户端抽象,再默认使用
ExternalClient
正确示例
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export class RatesAndBenefitsClient extends JanusClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(context, options)
  }
}
错误示例
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

export class RatesAndBenefitsClient extends ExternalClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(`https://${context.account}.vtexcommercestable.com.br`, context, options)
  }
}

Preferred pattern

推荐模式

Recommended file layout:
text
node/
├── clients/
│   ├── index.ts
│   ├── catalog.ts
│   └── partnerApi.ts
├── middlewares/
│   └── getData.ts
└── index.ts
Register native and custom clients in one place:
typescript
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'
import { PartnerApiClient } from './partnerApi'

export class Clients extends IOClients {
  public get catalog() {
    return this.getOrSet('catalog', Catalog)
  }

  public get partnerApi() {
    return this.getOrSet('partnerApi', PartnerApiClient)
  }
}
Create custom clients with explicit routes and options:
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

export class PartnerApiClient extends ExternalClient {
  private routes = {
    order: (id: string) => `/orders/${id}`,
  }

  constructor(context: IOContext, options?: InstanceOptions) {
    super('https://partner.example.com', context, {
      ...options,
      retries: 2,
      timeout: 2000,
      headers: {
        'X-VTEX-Account': context.account,
        'X-VTEX-Workspace': context.workspace,
        ...options?.headers,
      },
    })
  }

  public getOrder(id: string) {
    return this.http.get(this.routes.order(id), {
      metric: 'partner-get-order',
    })
  }
}
Wire shared client options in the runtime:
typescript
import type { ClientsConfig } from '@vtex/api'
import { Clients } from './clients'

const clients: ClientsConfig<Clients> = {
  implementation: Clients,
  options: {
    default: {
      retries: 2,
      timeout: 2000,
    },
  },
}
Use clients from handlers through
ctx.clients
:
typescript
export async function getOrder(ctx: Context) {
  const order = await ctx.clients.partnerApi.getOrder(ctx.vtex.route.params.id)
  ctx.body = order
}
If a client file grows too large, split it by bounded integration domains and keep
node/clients/index.ts
as a small registry.
推荐的文件结构:
text
node/
├── clients/
│   ├── index.ts
│   ├── catalog.ts
│   └── partnerApi.ts
├── middlewares/
│   └── getData.ts
└── index.ts
在统一位置注册原生和自定义客户端:
typescript
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'
import { PartnerApiClient } from './partnerApi'

export class Clients extends IOClients {
  public get catalog() {
    return this.getOrSet('catalog', Catalog)
  }

  public get partnerApi() {
    return this.getOrSet('partnerApi', PartnerApiClient)
  }
}
创建自定义客户端时明确定义路由和配置:
typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

export class PartnerApiClient extends ExternalClient {
  private routes = {
    order: (id: string) => `/orders/${id}`,
  }

  constructor(context: IOContext, options?: InstanceOptions) {
    super('https://partner.example.com', context, {
      ...options,
      retries: 2,
      timeout: 2000,
      headers: {
        'X-VTEX-Account': context.account,
        'X-VTEX-Workspace': context.workspace,
        ...options?.headers,
      },
    })
  }

  public getOrder(id: string) {
    return this.http.get(this.routes.order(id), {
      metric: 'partner-get-order',
    })
  }
}
在运行时配置通用客户端参数:
typescript
import type { ClientsConfig } from '@vtex/api'
import { Clients } from './clients'

const clients: ClientsConfig<Clients> = {
  implementation: Clients,
  options: {
    default: {
      retries: 2,
      timeout: 2000,
    },
  },
}
在处理函数中通过
ctx.clients
使用客户端:
typescript
export async function getOrder(ctx: Context) {
  const order = await ctx.clients.partnerApi.getOrder(ctx.vtex.route.params.id)
  ctx.body = order
}
如果单个客户端文件过大,可以按照集成领域拆分,保持
node/clients/index.ts
作为精简的注册入口。

Common failure modes

常见错误模式

  • Using
    axios
    ,
    fetch
    , or other raw HTTP libraries in backend handlers instead of VTEX IO clients.
  • Instantiating clients directly inside handlers instead of registering them in
    IOClients
    .
  • Choosing
    ExternalClient
    when a native VTEX client or a more specific app client already exists.
  • Putting business rules, validation, or orchestration into clients instead of keeping them as transport wrappers.
  • Scattering headers, auth setup, and retry settings across handlers instead of centralizing them in the client or shared client config.
  • Forgetting the outbound-access policy required for an external integration declared in a custom client.
  • 在后端处理函数中使用
    axios
    fetch
    或其他原生HTTP库,而非VTEX IO客户端。
  • 在处理函数中直接实例化客户端,而非在
    IOClients
    中注册。
  • 当已经有原生VTEX客户端或更具体的应用客户端时,仍然选择使用
    ExternalClient
  • 在客户端中实现业务规则、校验、流程编排逻辑,而不是仅将客户端作为传输层封装。
  • 在各个处理函数中零散配置请求头、鉴权逻辑、重试参数,而不是在客户端或共享客户端配置中统一管理。
  • 忘记在自定义客户端对应的外部集成中声明所需的出站访问策略。

Review checklist

审查清单

  • Does each integration use the correct VTEX IO client abstraction?
  • Are native clients from
    @vtex/api
    or
    @vtex/clients
    preferred when available?
  • Are clients registered in
    IOClients
    and consumed through
    ctx.clients
    ?
  • Are raw HTTP libraries absent from the backend integration code?
  • Are retries, timeouts, headers, and metrics configured in the client layer rather than scattered across handlers?
  • Are business rules kept out of the client layer?
  • 每个集成是否使用了正确的VTEX IO客户端抽象?
  • @vtex/api
    @vtex/clients
    有可用的原生客户端时是否优先使用?
  • 客户端是否在
    IOClients
    中注册并通过
    ctx.clients
    调用?
  • 后端集成代码中是否没有使用原生HTTP库?
  • 重试、超时、请求头、监控指标是否在客户端层配置,而非分散在各个处理函数中?
  • 业务逻辑是否没有放在客户端层实现?

Reference

参考资料