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 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 missing scope as a clean tool error. The dashboard token creator selects all public scopes by default — uncheck only what you want to deny.
Read scopes
| Scope | Tools |
|---|---|
brand_dna:read | brand_dna.get_brand_dna |
catalog:read | catalog.list_blanks |
credits:read | credits.check_credits |
design:read | design.get, design.list_designs, design.get_design, design.get_design_history |
insight:read | insight.get_overview, insight.list_design_performance, insight.get_guidance |
mockup:read | mockup.get, mockup.list_mockups, mockup.get_mockup |
listing:read | listing.get_listing, listing.list_listings, listing.get |
content:read | content.get, content.list |
social_post:read | social_post.list |
trend:read | trend.list_trends, trend.get_trend_match, trend.get, trend.list_seasonal_events |
optimize:read | optimize.list_providers, optimize.list_provider_products, optimize.check_duplicate, optimize.get |
Write scopes
| Scope | Tools |
|---|---|
brand_dna:write | brand_dna.set_brand_dna |
design:write | design.generate_design |
mockup:write | mockup.generate_mockup, mockup.update_mockup_selection, mockup.submit_mockup_feedback, mockup.retry_mockup |
listing:write | listing.create_listing, listing.update_listing, listing.regenerate_listing_field, listing.publish_listing, listing.delete_listing |
content:write | content.generate, content.delete |
social_post:write | social_post.generate, social_post.update, social_post.publish |
trend:write | trend.generate_launch_concept, trend.submit_trend_feedback |
optimize:write | optimize.optimize_product, 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.
Rate Limits
Rate limits apply per-PAT, per-tool, on a fixed 60-second window. Hitting one raises a clean rate limit exceeded error with retry_after_seconds.
| Tool | Limit |
|---|---|
design.generate_design | 10 / min |
mockup.generate_mockup | 20 / min |
mockup.update_mockup_selection, mockup.submit_mockup_feedback | 60 / min |
mockup.retry_mockup | 20 / min |
listing.create_listing | 10 / min |
listing.update_listing, listing.delete_listing | 20 / min |
listing.regenerate_listing_field | 10 / min |
listing.publish_listing | 5 / min |
content.generate | 5 / min |
content.delete | 20 / min |
social_post.generate | 10 / min |
social_post.publish | 10 / min |
trend.generate_launch_concept | 10 / min |
trend.submit_trend_feedback | 60 / min |
optimize.list_provider_products | 30 / min |
optimize.optimize_product | 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 by |
|---|---|---|
design.generate_design | 10 credits | MCP decorator (at dispatch) |
mockup.generate_mockup (quality="pro") | 2 credits per mockup | MCP decorator (at dispatch) |
mockup.generate_mockup (quality="standard") | Free | — |
listing.create_listing | 2 credits | MCP decorator (at dispatch) |
trend.generate_launch_concept | 2 credits — only on first dispatch | MCP decorator (at dispatch) |
content.generate (format="video") | 40 credits (social_video) | Worker (on successful completion) |
content.generate (format="slideshow") | 12 credits (social_slideshow) | Worker (on successful completion) |
content.generate (format="carousel") | 4 credits (social_carousel) | Worker (on successful completion) |
content.generate (format="single") | 1 credit (social_single) | Worker (on successful completion) |
Three pre-flight gates prevent surprises:
- Out-of-credit calls fail fast — the tool body runs a balance check before dispatching any Celery work.
- Idempotent tools (
generate_design,generate_mockup,create_listing,publish_listing,optimize_product,content.generate,generate_launch_concept) dedupe on retry, so a flaky-network loop doesn't double-bill. trend.generate_launch_conceptshort-circuits to free when the concept is already cached.
Read tools and status polls are always free.
Errors You Might See
| Symptom | Cause | Fix |
|---|---|---|
401 missing bearer token | No Authorization header | Re-check your client config; restart after changes |
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 limit exceeded — retry after Ns | Per-tool per-PAT limit hit | Respect retry_after_seconds; batch where possible |
insufficient credits: requires N, available M | Balance below cost | Top up credits, or drop quality="standard" for mockups |
does not belong to this organization | Cross-org UUID | Each PAT is pinned to one org — agents can't reach other orgs' designs / listings / etc. |