Personal Access Tokens (PATs)
PATs are long-lived, per-organization bearer tokens. Every MCP request carries one in the Authorization header:
Authorization: Bearer vbl_pat_live_<32 urlsafe chars>Token Format
| Component | Example | Meaning |
|---|---|---|
| Prefix | vbl_pat_live_ | Production. Registered with GitHub secret scanning. |
| Prefix | vbl_pat_test_ | Non-production environments. |
| Body | 32 URL-safe characters | 24 bytes of entropy from secrets.token_urlsafe(). |
At creation the full token is shown once. Only a SHA256 hash of the raw value is persisted — a stolen database row cannot be replayed as a token.
Lifecycle
Create
Dashboard → Settings → MCP → Create token. Choose a label and scopes. Up to ten active PATs per user per organization.
Rotate
Create a new token, swap your client config over, then revoke the old one. Tokens have no expiry — rotation is entirely on your schedule.
Revoke
Any row in the Settings list can be revoked. Rejection is immediate; in-flight requests that already passed auth complete normally.
Where To Store Tokens
Use one canonical local secret: VAYBEL_PAT.
For Claude Code, prefer storing the token in Claude Code settings so it is available to both the Vaybel MCP server and the official Vaybel Skills plugin:
{
"env": {
"VAYBEL_PAT": "vbl_pat_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}Then reference it from MCP config instead of pasting the token into the config file:
{
"mcpServers": {
"vaybel": {
"type": "http",
"url": "https://mcp.vaybel.com/",
"headers": {
"Authorization": "Bearer ${VAYBEL_PAT}"
}
}
}
}Do Not Commit Tokens
Do not put raw vbl_pat_live_... values in project .mcp.json, example repos, screenshots, issue comments, or chat transcripts. Avoid putting long-lived Vaybel tokens in broad shell startup files like .zshrc unless you intentionally want every terminal process to inherit them.
Scopes
Every tool declares the scope(s) it requires. A PAT missing the scope gets a missing_scope tool error. The dashboard token creator selects all public scopes by default — uncheck only what you want to deny. Scopes added to the platform after a token was minted are not retroactively granted: create a fresh token to pick up new scopes.
Read scopes
| Scope | Tools |
|---|---|
brand_dna:read | brand_dna.get, brand_dna.list_audience_presets |
catalog:read | catalog.list_blanks |
credits:read | credits.check, credits.list_costs, credits.list_usage |
design:read | design.get_generation, design.list, design.get, design.get_history |
insight:read | insight.get_overview, insight.list_design_performance, insight.get_guidance (all Growth plan) |
mockup:read | mockup.get_generation, mockup.list, mockup.get |
virtual_model:read | virtual_model.list, virtual_model.get_generation |
product_video:read | product_video.get_generation |
listing:read | listing.get, listing.list, listing.get_generation, listing.list_warehouses |
content:read | content.get, content.list |
social_post:read | social_post.get, social_post.list |
trend:read | trend.list, trend.get, trend.list_keywords, trend.get_keyword, trend.get_generation, trend.list_seasonal_events |
optimize:read | optimize.list_providers, optimize.list_provider_products, optimize.check_duplicate, optimize.get_generation |
Write scopes
| Scope | Tools |
|---|---|
brand_dna:write | brand_dna.set |
design:write | design.generate |
mockup:write | mockup.generate, mockup.update_selection, mockup.submit_feedback, mockup.retry |
virtual_model:write | virtual_model.generate |
product_video:write | product_video.generate |
listing:write | listing.create, listing.update, listing.regenerate_field, listing.publish, listing.delete |
content:write | content.generate, content.delete |
social_post:write | social_post.generate, social_post.update, social_post.publish |
trend:write | trend.generate_concept, trend.submit_feedback |
optimize:write | optimize.run, optimize.refresh_listing |
Principle of Least Privilege
A monitoring agent that polls status doesn't need any :write scope — give it the :read set and nothing more. A trend-discovery agent that proposes drafts can have all reads plus design:write + mockup:write, but not listing:write or content:write. Build narrow tokens.
Plan gates
The MCP surface is a paid capability. Every tool requires Starter or higher, with the Growth-plan feature areas gated at Growth:
| Tier | Tools |
|---|---|
| Open (any plan) | ping, credits.check — verify your connection and balance before upgrading |
| Starter+ | design.*, mockup.*, virtual_model.*, product_video.* (listing showcase videos), listing.*, trend.*, catalog.*, brand_dna.*, credits.list_costs / list_usage |
| Growth | optimize.* (AI Product Optimizer), content.* + social_post.* (social videos, slideshows, carousels & publishing), insight.* (Shop Insights) |
Gated calls fail with a clear plan_required: ... error rather than returning empty data, so agents can surface the upgrade path. Each tool's tier is shown as a Plan chip in the Tool Reference.
Rate Limits
Rate limits apply per-PAT, per-tool, on a fixed 60-second window. Hitting one raises a rate_limited error carrying a retry after Ns hint.
| Tool | Limit |
|---|---|
design.generate | 10 / min |
mockup.generate | 20 / min |
mockup.update_selection, mockup.submit_feedback | 60 / min |
mockup.retry | 20 / min |
virtual_model.generate | 10 / min |
product_video.generate | 10 / min |
brand_dna.set | 10 / min |
listing.create | 10 / min |
listing.update, listing.delete | 20 / min |
listing.regenerate_field | 10 / min |
listing.publish | 5 / min |
listing.list_warehouses | 10 / min |
content.generate | 5 / min |
content.delete | 20 / min |
social_post.generate | 10 / min |
social_post.publish | 10 / min |
trend.generate_concept | 10 / min |
trend.submit_feedback | 60 / min |
optimize.list_provider_products | 30 / min |
optimize.run | 10 / min |
optimize.refresh_listing | 20 / min |
| All other public tools | No explicit MCP-layer limit (platform-level limits still apply) |
Credit balance provides the finer gating for billable tools — you'll hit insufficient_credits before a rate limit on a real workload.
Credit Charging
Most tools are free. A handful charge from the same credit balance your dashboard uses — no separate developer meter.
| Tool | Cost | Billed |
|---|---|---|
design.generate | 10 credits | On successful completion (failed generations aren't charged) |
mockup.generate (quality="pro") | 2 credits per mockup | By the worker per successful render (failed renders aren't charged) |
mockup.generate (quality="standard") | Free | — |
virtual_model.generate | 1 credit per model | At dispatch |
product_video.generate | 20 credits per video | By the worker on success |
listing.create | 2 credits | At dispatch |
trend.generate_concept | 2 credits — only on first dispatch | At dispatch (cached concepts are free) |
content.generate (format="video") | 40 credits | By the worker on success |
content.generate (format="slideshow") | 12 credits | By the worker on success |
content.generate (format="carousel") | 4 credits | By the worker on success |
content.generate (format="single") | 1 credit | By the worker on success |
Three pre-flight gates prevent surprises:
- Out-of-credit calls fail fast — billable tools run a balance check before dispatching any background work.
- Billable tools accept an
idempotency_key; sending the same key on a retry returns the cached result, so a flaky-network loop doesn't double-bill (or double-publish). trend.generate_conceptshort-circuits to free when the concept is already cached.
Read tools and status polls are always free.
Errors You Might See
Tool errors arrive as MCP tool results (not HTTP status codes — only authentication failures are HTTP 401). Machine-readable errors are prefixed with a stable code:
| Error | Cause | Fix |
|---|---|---|
HTTP 401 missing bearer token | No Authorization header | Re-check your client config; restart after changes |
HTTP 401 unknown token | Revoked or typo'd PAT | Revoke from Settings, create a fresh one |
missing_scope: ... | PAT lacks the scope the tool needs | Re-create the PAT with the missing scope |
rate_limited: ... retry after Ns | Per-tool per-PAT limit hit | Wait the indicated seconds; batch where possible |
insufficient_credits: requires N, available M | Balance below cost | Top up credits, or use quality="standard" for mockups |
plan_required: ... | Tool needs a higher plan (trends → Starter+, insights → Growth) | Upgrade the org's plan |
not_found: ... | Unknown id — or a UUID belonging to another org | Each PAT is pinned to one org; re-check the id |
budget_exceeded: ... | Session budget cap reached (internal session tokens) | Raise the session budget |
Errors without a code prefix are plain validation messages (e.g. prompt is required).