VoidnetVoid Docs
Buyer guide

MCP Buyer Guide

Discover, purchase, and query MCP tools published on Voidnet Console

This guide covers everything you need to discover, purchase, and query apps published by developers on the Voidnet Console marketplace.

New to Voidnet? Get Started walks you through credentials and your first call.


Overview

A buyer is a user who discovers apps in the Voidnet Console marketplace and calls them through the Voidnet. The Voidnet handles authentication, rate limiting, metering, and billing — so you only need valid credentials and the app's public name.

Your Client (Key or OAuth Token)  →  Voidnet  →  Publisher's MCP Server

The Voidnet supports two authentication methods:

  • API Keys (vnb-sk-* / vai-sk-*) — Simple, long-lived secrets for individual developers
  • OAuth Access Tokens (JWT) — Short-lived tokens for production systems

Prerequisites

  • A Voidnet Console account (sign up)
  • An API key for authentication (see below)
  • Basic understanding of JSON-RPC 2.0 and MCP protocol

Authentication

API Keys

API keys are the simplest way to authenticate. Generate them from the API Keys page under the Buyer section of your Console dashboard:

PrefixTypeUse Case
vnb-sk-Standard Buyer Service KeyGeneral purpose
vai-sk-Ambient KeyAutomated/integrated workflows
vnb-sk-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6

Security rules:

  • Save your key immediately — it's shown only once at creation
  • Never expose keys in client-side code, version control, or logs
  • Rotate keys periodically from the Console
  • Revoke compromised keys immediately

OAuth Access Tokens

For production systems, you can use OAuth 2.0 access tokens instead of raw API keys. The Voidnet runs its own authorization server and supports one grant type:

Grant TypeUse Case
client_credentialsExchange your API key for a short-lived JWT

Access tokens are JSON Web Tokens (JWTs) signed with RS256. They expire after a configurable period (default 1 hour) and can be used anywhere you'd use an API key.

When to use which

SituationRecommended
Local development, testingAPI key (vnb-sk-*)
Production services, CI/CDJWT access token (client_credentials)
Highly sensitive environmentsRotate API key → short-lived JWT per session

Getting an Access Token

From an API Key (client_credentials)

Exchange your vnb-sk-* key for a JWT access token:

curl -X POST http://localhost:8080/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=vnb-sk-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6"

Successful response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "mcp:tools"
}

The returned access_token is a JWT you can use in the Authorization header instead of your raw API key.

Notes:

  • The client_id is your full API key — no separate client_secret is needed
  • A scope parameter may be provided (optional, defaults to mcp:tools)
  • The aud claim is always the Voidnet issuer URL — the token endpoint accepts no client-controlled audience input

Token expiration

Access tokens have an exp claim. When a token expires, the Voidnet returns api_key_expired (401). You should:

  1. Detect the 401 response
  2. Request a new token from /oauth/token
  3. Retry the request with the new token

Apps and Purchasing

Browse the Marketplace. Each app lists its publisher, name, description, tier (free/paid), and capabilities. To query an app you need the publisher's username and app name — these form the public route.

  • Free tier: click "Get" to start using immediately (rate limits apply).
  • Paid tier: click Purchase/Subscribe and complete Stripe Checkout.
  • Rules: one active purchase per buyer per app; upgrades allowed, downgrades not; meter resets on a rolling 30-day window.

Making Requests

All app requests go through the Voidnet at a single endpoint.

POST http://localhost:8080/v1-beta/{adapter}/{username}/{appname}
ParameterDescriptionExample
adapterProtocol adapter type (mcp)mcp
usernamePublisher's public usernameacmecorp
appnameApp name (unique per publisher)weather-tools
HeaderRequiredDescription
Authorization: Bearer <credential>YesYour API key (vnb-sk-*) or JWT access token (eyJ...)
Content-Type: application/jsonYesRequest body format
MCP-Session-IdNoSession ID for stateful connections

The Voidnet detects whether your credential is an API key (starts with vnb-sk- or vai-sk-) or a JWT (3 dot-separated base64 segments) and validates it accordingly.

Request body

The body is a standard JSON-RPC 2.0 request:

{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "tools/list",
  "params": {}
}

Complete example (API key)

Discover available tools from acmecorp/weather-tools:

curl -X POST http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools \
  -H "Authorization: Bearer vnb-sk-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "tools/list"
  }'

Complete example (JWT access token)

TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

curl -X POST http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "tools/list"
  }'

MCP Methods

The apps you query speak the MCP protocol (version 2025-11-25). Here are the methods you can call:

tools/list

Discover what tools an app provides:

curl -X POST http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools \
  -H "Authorization: Bearer vnb-sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "tools/list"
  }'

Response:

{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "tools": [
      {
        "name": "get_forecast",
        "description": "Get weather forecast for a location",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": { "type": "string", "description": "City name or coordinates" },
            "days": { "type": "integer", "description": "Number of days (1-7)" }
          },
          "required": ["location"]
        }
      }
    ]
  }
}

tools/call

Execute a tool with arguments:

curl -X POST http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools \
  -H "Authorization: Bearer vnb-sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "2",
    "method": "tools/call",
    "params": {
      "name": "get_forecast",
      "arguments": {
        "location": "London",
        "days": 3
      }
    }
  }'

Response:

{
  "jsonrpc": "2.0",
  "id": "2",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"location\":\"London\",\"forecast\":[{\"day\":\"Monday\",\"high\":18,\"low\":12},{\"day\":\"Tuesday\",\"high\":20,\"low\":13},{\"day\":\"Wednesday\",\"high\":17,\"low\":11}]}"
      }
    ]
  }
}

prompts/list

Some apps expose prompt templates:

curl -X POST http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools \
  -H "Authorization: Bearer vnb-sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "3",
    "method": "prompts/list"
  }'

prompts/get

Retrieve a specific prompt template:

curl -X POST http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools \
  -H "Authorization: Bearer vnb-sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "4",
    "method": "prompts/get",
    "params": {
      "name": "weather_summary",
      "arguments": { "location": "London" }
    }
  }'

resources/list

Discover available data resources:

curl -X POST http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools \
  -H "Authorization: Bearer vnb-sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "5",
    "method": "resources/list"
  }'

resources/read

Read a specific resource by URI:

curl -X POST http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools \
  -H "Authorization: Bearer vnb-sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "6",
    "method": "resources/read",
    "params": {
      "uri": "weather://London/current"
    }
  }'

OAuth Endpoints

The Voidnet exposes the following OAuth 2.0 endpoints for programmatic discovery and token acquisition:

EndpointDescription
POST /oauth/tokenIssue access tokens (client_credentials)
GET /.well-known/oauth-authorization-serverRFC 8414 AS metadata
GET /.well-known/oauth-protected-resourceRFC 9728 resource metadata
GET /.well-known/jwks.jsonVoidnet's public signing keys

AS metadata (RFC 8414)

{
  "issuer": "http://localhost:8080",
  "token_endpoint": "http://localhost:8080/oauth/token",
  "jwks_uri": "http://localhost:8080/.well-known/jwks.json",
  "grant_types_supported": ["client_credentials"],
  "token_endpoint_auth_methods_supported": ["client_secret_post"],
  "scopes_supported": ["mcp:tools"],
  "response_types_supported": ["token"]
}

Protected resource metadata (RFC 9728)

{
  "authorization_servers": ["http://localhost:8080"],
  "scopes_supported": ["mcp:tools"],
  "bearer_methods_supported": ["authorization_header"]
}

These endpoints let OAuth-aware clients configure themselves automatically without hardcoding token endpoints or signing keys.


Error Handling

The Voidnet returns errors in one JSON shape. The error field is an object, not a string:

{
  "error": {
    "code": "error_code",
    "message": "Human-readable description",
    "status": 429
  }
}

For the complete catalog, see the Error Reference.


Rate Limits & Metering

The Voidnet enforces a per-minute/per-day rate limit and a monthly meter limit per app. Exceeding either returns a 429:

  • rate_limit_exceeded — too fast. Wait for the window to reset and retry with backoff.
  • meter_limit_exceeded — monthly quota exhausted. Wait for the rolling 30-day reset or upgrade your tier.
  • meter_expired — billing period ended. Wait for the meter to reset.

There are no X-RateLimit-* response headers. Track your remaining quota in the Console under Usage.


Calling Apps from Code

  • Rate limit (429) — Wait and retry. Limits reset per minute or per day depending on the layer.
  • Meter limit (429) — Your monthly quota is exhausted. Either wait for the reset (rolling 30-day window) or upgrade to a paid tier if available.

Calling Apps from Code

TypeScript / JavaScript

const API_KEY = "vnb-sk-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6";
const TOKEN_ENDPOINT = "http://localhost:8080/oauth/token";

// Option A: API key (simple)
async function callWithKey() {
  const response = await fetch("http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "1",
      method: "tools/call",
      params: {
        name: "get_forecast",
        arguments: { location: "London", days: 3 }
      }
    })
  });
  return await response.json();
}

// Option B: Get an access token first (production)
async function getAccessToken(): Promise<string> {
  const resp = await fetch(TOKEN_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: API_KEY
    })
  });
  const data = await resp.json();
  return data.access_token;
}

async function callWithToken() {
  const token = await getAccessToken();
  const response = await fetch("http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "1",
      method: "tools/list"
    })
  });
  return await response.json();
}

Python

import requests

API_KEY = "vnb-sk-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6"

# Option A: API key (simple)
response = requests.post(
    "http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    },
    json={
        "jsonrpc": "2.0",
        "id": "1",
        "method": "tools/call",
        "params": {
            "name": "get_forecast",
            "arguments": {"location": "London", "days": 3}
        }
    }
)
data = response.json()
print(data["result"])

# Option B: Get an access token first (production)
token_resp = requests.post(
    "http://localhost:8080/oauth/token",
    data={
        "grant_type": "client_credentials",
        "client_id": API_KEY
    }
)
token = token_resp.json()["access_token"]

response = requests.post(
    "http://localhost:8080/v1-beta/mcp/acmecorp/weather-tools",
    headers={
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    },
    json={"jsonrpc": "2.0", "id": "1", "method": "tools/list"}
)

cURL (reusable)

GATEWAY="http://localhost:8080"
API_KEY="vnb-sk-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6"
USERNAME="acmecorp"
APPNAME="weather-tools"

# Authenticate with API key directly
curl -X POST "$GATEWAY/v1-beta/mcp/$USERNAME/$APPNAME" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "tools/list"
  }'

# Or exchange for a JWT access token first
TOKEN=$(curl -s -X POST "$GATEWAY/oauth/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=$API_KEY" | jq -r '.access_token')

curl -X POST "$GATEWAY/v1-beta/mcp/$USERNAME/$APPNAME" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "tools/list"
  }'

FAQ

Do I need to purchase an app before calling it?

Yes — even free tiers require a purchase record. Click "Get" in the marketplace to activate a free app. This creates the necessary record for the Voidnet to authorize your requests.

How do I find which apps I've purchased?

Go to the Usage page in your Console dashboard under the Buyer section. You'll see all apps you've purchased, their tier status, API call counts, and remaining meter usage.

Can I use the same API key for multiple apps?

Yes — a single API key works across all apps you purchase.

How do I know which methods an app supports?

Call tools/list, prompts/list, and resources/list on the app to discover its capabilities. These are standard MCP discovery methods.

What's the difference between vnb-sk- and vai-sk- keys?

Both authenticate you to the Voidnet. vnb-sk- keys are standard buyer service keys created from the Console. vai-sk- keys are ambient keys used for integrated or automated workflows.

When should I use a JWT access token instead of my API key?

Use JWT access tokens in production systems, CI/CD pipelines, and shared environments. They're short-lived (default 1 hour), reducing the risk of credential exposure. Use raw API keys for local development and testing.

Can I use both API keys and JWT tokens?

Yes — the Voidnet accepts both. OAuth access tokens (eyJ...) and API keys (vnb-sk-* / vai-sk-*) are both valid in the Authorization: Bearer header. The Voidnet detects the format automatically.

What happens when my access token expires?

The Voidnet returns api_key_expired (401). Request a new token from POST /oauth/token using your API key, then retry.

Can my organization use Okta or Entra ID to authenticate?

Not yet. The Voidnet authorization server issues tokens via client_credentials — exchange your API key for a short-lived JWT. Enterprise identity federation (jwt-bearer) is not currently supported.

On this page