
vtex-io-service-apps
Apply when building backend service apps under node/ in a VTEX IO project or configuring service.json routes. Covers the Service class, middleware functions, ctx.clients pattern, JanusClient, ExternalClient, MasterDataClient, and IOClients registration. Use for implementing backend APIs, event handlers, or integrations that must use @vtex/api clients instead of raw HTTP libraries.
- Installs
- 1.2K
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/vtex/skills --skill vtex-io-service-apps- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads vtex-io-service-apps automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Backend Service Apps & API Clients
When this skill applies
Use this skill when developing a VTEX IO app that needs backend logic — REST API routes, GraphQL resolvers, event handlers, scheduled tasks, or integrations with VTEX Commerce APIs and external services.
- Building the Service entry point (
node/index.ts) with typed context, clients, and state - Creating and registering custom clients extending JanusClient or ExternalClient
- Using
ctx.clientsto access clients with built-in caching, retry, and metrics - Configuring routes and middleware chains in service.json
Do not use this skill for:
- Manifest and builder configuration (use
vtex-io-app-structureinstead) - GraphQL schema definitions (use
vtex-io-graphql-apiinstead) - React component development (use
vtex-io-react-appsinstead)
Decision rules
- The Service class (
node/index.ts) is the entry point for every VTEX IO backend app. It receives clients, routes (with middleware chains), GraphQL resolvers, and event handlers. - Every middleware, resolver, and event handler receives
ctxwith:ctx.clients(registered clients),ctx.state(mutable per-request state),ctx.vtex(auth tokens, account info),ctx.body(request/response body). - Use
JanusClientfor VTEX internal APIs (base URL:https://{account}.vtexcommercestable.com.br). - Use
ExternalClientfor non-VTEX APIs (any URL you specify). - Use
AppClientfor routes exposed by other VTEX IO apps. - Use
MasterDataClientfor Master Data v2 CRUD operations. - Register custom clients by extending
IOClients— each client is lazily instantiated on first access viathis.getOrSet(). - Keep clients as thin data-access wrappers. Put business logic in middlewares or service functions.
Client hierarchy:
| Class | Use Case | Base URL |
|---|---|---|
JanusClient | Access VTEX internal APIs (Janus gateway) | https://{account}.vtexcommercestable.com.br |
ExternalClient | Access external (non-VTEX) APIs | Any URL you specify |
AppClient | Access routes exposed by other VTEX IO apps | https://{workspace}--{account}.myvtex.com |
InfraClient | Access VTEX IO infrastructure services | Internal |
MasterDataClient | Access Master Data v2 CRUD operations | VTEX API |
Architecture:
Request → VTEX IO Runtime → Service
├── routes → middleware chain → ctx.clients.{name}.method()
├── graphql → resolvers → ctx.clients.{name}.method()
└── events → handlers → ctx.clients.{name}.method()
│
▼
Client (JanusClient / ExternalClient)
│
▼
External Service / VTEX API
Hard constraints
Constraint: Use @vtex/api Clients — Never Raw HTTP Libraries
All HTTP communication from a VTEX IO service app MUST go through @vtex/api clients (JanusClient, ExternalClient, AppClient, or native clients from @vtex/clients). You MUST NOT use axios, fetch, got, node-fetch, or any other raw HTTP library.
Why this matters
VTEX IO clients provide automatic authentication header injection, built-in caching (disk and memory), retry with exponential backoff, timeout management, native metrics and billing tracking, and proper error handling. Raw HTTP libraries bypass all of these. Additionally, outbound traffic from VTEX IO is firewalled — only @vtex/api clients properly route through the infrastructure.
Detection
If you see import axios from 'axios', import fetch from 'node-fetch', import got from 'got', require('node-fetch'), or any direct fetch() call in a VTEX IO service app, STOP. Replace with a proper client extending JanusClient or ExternalClient.
Correct
import type { InstanceOptions, IOContext } 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-Api-Key': 'my-key',
...options?.headers,
},
})
}
public async getForecast(city: string): Promise<Forecast> {
return this.http.get(`/v1/forecast/${city}`, {
metric: 'weather-forecast',
})
}
}
Wrong
import axios from 'axios'
// This bypasses VTEX IO infrastructure entirely.
// No caching, no retries, no metrics, no auth token injection.
// Outbound requests may be blocked by the firewall.
export async function getForecast(city: string): Promise<Forecast> {
const response = await axios.get(`https://api.weather.com/v1/forecast/${city}`, {
headers: { 'X-Api-Key': 'my-key' },
})
return response.data
}
Constraint: Access Clients via ctx.clients — Never Instantiate Directly
Clients MUST always be accessed through ctx.clients.{clientName} in middlewares, resolvers, and event handlers. You MUST NOT instantiate client classes directly with new.
Why this matters
The IOClients registry manages client lifecycle, ensuring proper initialization with the current request's IOContext (account, workspace, auth tokens). Direct instantiation creates clients without authentication context, without caching configuration, and without connection to the metrics pipeline.
Detection
If you see new MyClient(...) or new ExternalClient(...) inside a middleware or resolver, STOP. The client should be registered in the Clients class and accessed via ctx.clients.
Correct
// node/clients/index.ts
import { IOClients } from '@vtex/api'
import { CatalogClient } from './catalogClient'
export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', CatalogClient)
}
}
// node/middlewares/getProduct.ts
export async function getProduct(ctx: Context, next: () => Promise<void>) {
const { clients: { catalog } } = ctx
const product = await catalog.getProductById(ctx.query.id)
ctx.body = product
ctx.status = 200
await next()
}
Wrong
// node/middlewares/getProduct.ts
import { CatalogClient } from '../clients/catalogClient'
export async function getProduct(ctx: Context, next: () => Promise<void>) {
// Direct instantiation — no auth context, no caching, no metrics
const catalog = new CatalogClient(ctx.vtex, {})
const product = await catalog.getProductById(ctx.query.id)
ctx.body = product
ctx.status = 200
await next()
}
Constraint: Avoid Monolithic Service Apps
A single service app SHOULD NOT define more than 10 HTTP routes. If you need more, consider splitting into focused microservice apps.
Why this matters
VTEX IO apps run in containers with limited memory (max 512MB). A monolithic app with many routes increases memory usage, cold start time, and blast radius of failures. The VTEX IO platform is designed for small, focused apps that compose together.
Detection
If service.json defines more than 10 routes, warn the developer to consider splitting the app into smaller services. This is a soft limit — there may be valid exceptions.
Correct
{
"memory": 256,
"timeout": 30,
"routes": {
"get-reviews": { "path": "/_v/api/reviews", "public": false },
"get-review": { "path": "/_v/api/reviews/:id", "public": false },
"create-review": { "path": "/_v/api/reviews", "public": false },
"moderate-review": { "path": "/_v/api/reviews/:id/moderate", "public": false }
}
}
Wrong
{
"memory": 512,
"timeout": 60,
"routes": {
"route1": { "path": "/_v/api/reviews" },
"route2": { "path": "/_v/api/reviews/:id" },
"route3": { "path": "/_v/api/products" },
"route4": { "path": "/_v/api/products/:id" },
"route5": { "path": "/_v/api/orders" },
"route6": { "path": "/_v/api/orders/:id" },
"route7": { "path": "/_v/api/users" },
"route8": { "path": "/_v/api/users/:id" },
"route9": { "path": "/_v/api/categories" },
"route10": { "path": "/_v/api/categories/:id" },
"route11": { "path": "/_v/api/brands" },
"route12": { "path": "/_v/api/inventory" }
}
}
12 routes covering reviews, products, orders, users, categories, brands, and inventory — this should be 3-4 separate apps.
Preferred pattern
Define custom clients:
// node/clients/catalogClient.ts
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'
export class CatalogClient extends JanusClient {
constructor(context: IOContext, options?: InstanceOptions) {
super(context, {
...options,
headers: {
VtexIdclientAutCookie: context.authToken,
...options?.headers,
},
})
}
public async getProduct(productId: string): Promise<Product> {
return this.http.get(`/api/catalog/pvt/product/${productId}`, {
metric: 'catalog-get-product',
})
}
public async listSkusByProduct(productId: string): Promise<Sku[]> {
return this.http.get(`/api/catalog_system/pvt/sku/stockkeepingunitByProductId/${productId}`, {
metric: 'catalog-list-skus',
})
}
}
Register clients in IOClients:
// node/clients/index.ts
import { IOClients } from '@vtex/api'
import { CatalogClient } from './catalogClient'
import { ReviewStorageClient } from './reviewStorageClient'
export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', CatalogClient)
}
public get reviewStorage() {
return this.getOrSet('reviewStorage', ReviewStorageClient)
}
}
Create middlewares using ctx.clients and ctx.state:
// node/middlewares/getReviews.ts
import type { ServiceContext } from '@vtex/api'
import type { Clients } from '../clients'
type Context = ServiceContext<Clients>
export async function validateParams(ctx: Context, next: () => Promise<void>) {
const { productId } = ctx.query
if (!productId || typeof productId !== 'string') {
ctx.status = 400
ctx.body = { error: 'productId query parameter is required' }
return
}
ctx.state.productId = productId
await next()
}
export async function getReviews(ctx: Context, next: () => Promise<void>) {
const { productId } = ctx.state
const reviews = await ctx.clients.reviewStorage.getByProduct(productId)
ctx.status = 200
ctx.body = reviews
await next()
}
Wire everything in the Service entry point:
// node/index.ts
import type { ParamsContext, RecorderState } from '@vtex/api'
import { Service, method } from '@vtex/api'
import { Clients } from './clients'
import { validateParams, getReviews } from './middlewares/getReviews'
import { createReview } from './middlewares/createReview'
import { resolvers } from './resolvers'
export default new Service<Clients, RecorderState, ParamsContext>({
clients: {
implementation: Clients,
options: {
default: {
retries: 2,
timeout: 5000,
},
catalog: {
retries: 3,
timeout: 10000,
},
},
},
routes: {
reviews: method({
GET: [validateParams, getReviews],
POST: [createReview],
}),
},
graphql: {
resolvers: {
Query: resolvers.queries,
Mutation: resolvers.mutations,
},
},
})
Common failure modes
- Using axios/fetch/got/node-fetch for HTTP calls: These libraries bypass the entire VTEX IO infrastructure — no automatic auth token injection, no caching, no retry logic, no metrics. Outbound requests may also be blocked by the firewall. Create a proper client extending
ExternalClientorJanusClientinstead. - Putting business logic in clients: Clients become bloated and hard to test. Keep clients as thin wrappers around HTTP calls. Put business logic in middlewares or dedicated service functions.
- Direct client instantiation: Using
new MyClient(...)inside a middleware creates clients without auth context, caching, or metrics. Always access viactx.clients.
Review checklist
- Are all HTTP calls going through
@vtex/apiclients (no axios, fetch, got)? - Are all clients accessed via
ctx.clients, never instantiated withnew? - Are custom clients registered in the IOClients class?
- Does the Service entry point correctly wire clients, routes, resolvers, and events?
- Is business logic in middlewares/resolvers, not in client classes?
- Does
service.jsonhave reasonable route count (≤10)? - Are client options (retries, timeout) configured appropriately?
Reference
- Services — Overview of VTEX IO backend service development
- Clients — Native client list and client architecture overview
- Developing Clients — Step-by-step guide for creating custom JanusClient and ExternalClient
- Using Node Clients — How to use @vtex/api and @vtex/clients in middlewares and resolvers
- Calling Commerce APIs — Tutorial for building a service app that calls VTEX Commerce APIs
- Best Practices for Avoiding Rate Limits — Why clients with caching prevent rate-limit issues
FAQ
What does the vtex-io-service-apps skill do?
Apply when building backend service apps under node/ in a VTEX IO project or configuring service.json routes. Covers the Service class, middleware functions, ctx.clients pattern, JanusClient, ExternalClient, MasterDataClient, and IOClients registration. Use for implementing backend APIs, event handlers, or integrations that must use @vtex/api clients instead of raw HTTP libraries.
How do I install vtex-io-service-apps?
Run npx skills add https://github.com/vtex/skills --skill vtex-io-service-apps in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/vtex-io-service-apps/ (personal) or .claude/skills/vtex-io-service-apps/ (project).
Which AI agents can use vtex-io-service-apps?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is vtex-io-service-apps safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains vtex-io-service-apps?
It is published by vtex in vtex/skills. It has 1,242 recorded installs.
More from vtex/skills
vtex-io-react-apps
vtex/skills
Apply when building React components under react/ or configuring store blocks in store/ for VTEX IO apps. Covers interfaces.json, contentSchemas.json for Site Editor, VTEX Styleguide for admin apps, and css-handles for storefront styling. Use for creating custom storefront components, admin panels, pixel apps, or any frontend development within the VTEX IO react builder ecosystem.
vtex-io-masterdata
vtex/skills
Apply when working with MasterData v2 entities, schemas, or MasterDataClient in VTEX IO apps, or when anyone designing or implementing a solution must scrutinize whether Master Data is the correct storage. The skill prompts hard questions: native Catalog or other VTEX stores, OMS, or an external database may be better; do not default to MD because it is convenient. Covers JSON Schema, CRUD, triggers, search and scroll, schema lifecycle, purchase-path avoidance, single source of truth, and BFF handoffs. Use for justified custom persistence while avoiding the 60-schema limit.
vtex-io-graphql-api
vtex/skills
Apply when working with GraphQL schema files in graphql/ or implementing resolvers in node/resolvers/ for VTEX IO apps. Covers schema.graphql definitions, @cacheControl and @auth directives, custom type definitions, and resolver registration in the Service class. Use for exposing data through GraphQL queries and mutations with proper cache control and authentication enforcement.
vtex-io-storefront-react
vtex/skills
Apply when building VTEX IO storefront components under react/ for Store Framework experiences. Covers storefront component structure, css-handles, storefront context hooks, browser-safe data fetching, and how components should behave when used as theme blocks. Use for custom storefront UI, product widgets, banners, forms, or reviewing shopper-facing React code in VTEX IO apps.
vtex-io-app-settings
vtex/skills
Apply when defining, validating, or consuming VTEX IO app settings. Covers settingsSchema, app-level configuration boundaries, and how backend or frontend code should depend on settings safely. Use for merchant-configurable behavior, settings forms, or reviewing whether settings belong in app configuration rather than hardcoded logic or custom data entities.
vtex-io-application-performance
vtex/skills
Apply when improving VTEX IO Node or .NET services for latency, throughput, and resilience: in-process LRU, VBase, stale-while-revalidate, AppSettings loading, request context, parallel client calls, and avoiding duplicate work. Covers application-level performance patterns that complement edge/CDN caching. Use when optimizing backends beyond route-level Cache-Control.
Popular backend & apis skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
entra-app-registration
microsoft/azure-skills
Guides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.
google-agents-cli-adk-code
google/agents-cli
This skill should be used when the user wants to "write agent code", "build an agent with ADK", "add a tool", "create a callback", "define an agent", "use state management" — in a project that needs ADK (Agent Development Kit) API patterns and code examples. It provides a quick reference for agent types, tool definitions, orchestration patterns, callbacks, state management, the graph Workflow API, and reference recipes to study. Do NOT use for scaffolding (use google-agents-cli-scaffold) or deployment (use google-agents-cli-deploy).
embedded-captions
heygen-com/hyperframes
Add captions or subtitles to an existing single-subject talking-head video without editing the footage. Use for plain verbatim captions, cinematic captions embedded behind the subject, VFX captions, “炸/特效/酷炫字幕,” or a named identity from the 35-style catalog. Route by visual identity, not by backend engine. The quiet anchor rail is the default; embed every word only when the user explicitly wants a fully cinematic treatment. The workflow runs locally end to end, including transcription and subject matting; split multi-shot footage before applying it.
entra-agent-id
microsoft/azure-skills
Provision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmipath, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmipath token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registration (use entra-app-registration), Microsoft Foundry agent authoring (use microsoft-foundry).
neon
neondatabase/agent-skills
Overview of Neon, a complete set of cloud backend primitives around Lakebase Postgres: Auth, Object Storage, Functions, the AI Gateway, and Realtime. Start here to choose Neon for undecided login, files, APIs, and LLM calls, set up the CLI or MCP server, and follow the branch-first workflow. Use when building an app or backend on Neon, or when "Neon" or "Lakebase Postgres" is mentioned. Child skill neon-postgres wins for an existing DATABASEURL, SQL, schema, inspect, or search. Child skill neon-auth wins for login, users, sessions, identity routing, and Managed Better Auth setup. Child skill neon-realtime wins for live queries in reactive apps and agents. Also use for object storage, S3, buckets, serverless functions, function triggers, cron, AI gateway, LLM calls, realtime, logs, Loki, Grafana, observability, postgres, database, backend, Claimable Neon, neon.new, or a no-signup database.