The commands below assume the Docker deployment unless stated otherwise. Never
paste Stellar secret keys, encryption keys, API keys, signed authorization
entries, or database passwords into issues or shared logs.
First checks
From the repository root (openx402/):
/health/ready returns 503 only when the facilitator cannot execute SELECT 1
against PostgreSQL. Its search object reports search degradation but does not
gate readiness. Stellar RPC, Horizon, and account balances are checked during
startup, not continuously by this endpoint.
Facilitator is not ready
Symptom: /health/ready returns HTTP 503 and status: "not_ready".
Likely cause: PostgreSQL is unavailable, credentials are wrong, migrations
did not complete, or the connection pool cannot connect.
From the repository root (openx402/):
Check POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, and the facilitator’s
DATABASE_URL. The Compose database is intentionally not exposed on the host.
The facilitator runs forward-only migrations before opening its HTTP listener.
PostgreSQL connection failure
Symptom: the facilitator container restarts before listening, or logs a
connection timeout/authentication error.
Check: database_url_env in the selected facilitator YAML names the
environment variable that must contain the URL. The checked-in profiles use
DATABASE_URL. Compose constructs it from the Postgres variables.
From the repository root (openx402/):
Do not publish docker compose config output because it may contain resolved
secrets.
pgvector is unavailable
Symptom: search works lexically, but /health/ready shows
search.vectorSupport: false and semantic search is degraded.
Expected detail: pgvector is not installed on this PostgreSQL server; running lexical-only.
From the repository root (openx402/):
The checked-in Compose image includes pgvector. On another PostgreSQL service,
install/enable the extension with sufficient database privileges, or accept the
supported lexical-only mode. Payment endpoints are unaffected.
Semantic search is degraded
Symptom: /health/ready is 200, but search.semantic is degraded or
disabled; search responses may report partial results.
From the repository root (openx402/):
Check these exact keys:
search.semantic.enabled
search.semantic.provider
search.semantic.remote_url_env
search.semantic.remote_api_key_env
search.semantic.timeout_ms
search.semantic.dimension
search.models.offline
search.models.require_pinned_revision
The stock Docker image does not include @huggingface/transformers; the local
BGE-M3 profile therefore degrades to lexical unless you build a custom image.
OpenRouter returns 401 or times out
Symptom: worker status/detail or lastError says remote provider returned 401 Unauthorized, or embedding jobs time out and retry.
For the Railway profile, check both:
The profile’s exact config keys are
search.semantic.remote_url_env: FACILITATOR_EMBEDDING_URL and
search.semantic.remote_api_key_env: OPENROUTER_API_KEY. The provider’s
startup health check validates only that a URL exists; credential failure
appears after an embedding job runs. Calls are bounded by
search.semantic.timeout_ms, currently 5000 ms in the Railway profile.
Search falls back to lexical retrieval. Fix the key/endpoint, then allow queued
jobs to retry. /health/ready continues to expose current provider status and
detail.
No catalog results
Symptom: discovery endpoints return empty arrays after a seller emitted a
402 challenge.
A 402 challenge alone does not unconditionally activate a listing. Check:
indexing.auto_catalog: true;
indexing.index_on (verified in hosted/self-hosted profiles);
- that a valid
/verify actually succeeded;
- that the paid payload carried a valid
extensions.bazaar declaration;
- that the searched filters match the cataloged network/scheme/asset;
discovery.include_stale and discovery.include_unverified;
- whether
stale_after_hours demoted the resource.
From any directory:
The asset filter is implementation-specific. There is no price filter.
Symptom: payment succeeds, but the EXTENSION-RESPONSES header contains
bazaar.status: "rejected".
This is expected soft-failure behavior: bad catalog data cannot invalidate a
payment. Decode the base64 JSON header and inspect rejectedReason. Common
public reasons include schema failure, oversized metadata, missing URL,
insecure/local origin, missing MCP tool name, payTo ownership conflict, and
catalog write failure.
Check these exact policy keys:
indexing.require_valid_schema
indexing.max_metadata_bytes
indexing.max_schema_bytes
indexing.max_example_bytes
catalog_security.require_https_origins
catalog_security.allow_local_origins
Operator-only internal rejection details are in catalog observations/analytics,
not in public Bazaar discovery responses.
Seller URL is rejected
Symptom: rejectedReason says the resource must use HTTPS or is not publicly
routable.
The hosted Railway profile requires HTTPS and rejects loopback, private, and
link-local addresses. Use an HTTPS tunnel and make resource.url the exact
public URL buyers call.
The self-hosted local profile permits local origins for testnet development.
Pubnet startup forbids catalog_security.allow_local_origins: true and requires
require_https_origins: true.
mcp:// is a logical MCP identity, not a directly connectable endpoint. A paid
private MCP rejects it unless network_security.resolved_mcp_endpoints maps it
to an operator-verified HTTPS endpoint.
Buyer receives HTTP 402 repeatedly
Symptom: the paid retry receives another PaymentRequired response.
Check, without logging the signed payload:
- the second request repeats the same seller method, URL, query, and body;
- payment headers were encoded from the selected PaymentRequired;
- network, asset, atomic amount,
payTo, and timeout equal accepted terms;
- the payer signed the correct auth entry before expiry;
- seller and buyer use the same facilitator/network;
- the seller middleware can reach
/verify and /settle;
- the buyer reused one payment identifier for the logical request.
Inspect the seller’s safe error output and facilitator invalidReason or
errorReason. Do not print BUYER_SECRET_KEY or authorization XDR.
Wrong Stellar network
Symptom: unsupported_network, network_mismatch, or a signature that
fails despite apparently correct terms.
Check all copies use exactly stellar:testnet or stellar:pubnet. A Soroban
signature is bound to the network passphrase; a testnet authorization cannot be
replayed on pubnet. The checked-in profiles enable only stellar:testnet.
From any directory:
Buyer lacks the payment asset
Symptom: record/enforcing simulation rejects the transfer or settlement,
often as invalid_stellar_*_simulation_failed.
Check the buyer’s balance for the exact SEP-41 contract in
paymentRequirements.asset. Atomic units are integer strings; do not assume all
assets have seven decimals. Fee sponsorship supplies XLM transaction fees, not
the payment asset.
Seller trustline is missing
Symptom: an issued asset transfer fails during simulation or settlement.
For classic issued assets represented by a Stellar Asset Contract, confirm the
seller account can hold the underlying asset and has any required trustline.
Native XLM does not require a trustline. Also check authorization-required,
paused, or clawback asset state. These token failures can surface as enforcing
simulation failure; the facilitator does not create seller trustlines.
No channel account is available
Symptom: /settle returns HTTP 503, Retry-After: 1, and
errorReason: "settle_stellar_temporarily_unavailable".
The public response intentionally combines busy causes. Possible causes include
all channels leased/quarantined, an identical settlement already owned by
another replica, or the unresolved-settlement limit.
From the repository root (openx402/):
Do not restart merely to clear an unknown transaction. Channel/settlement state
is durable and recovery resumes polling. Check
limits.channel_lease_ms, limits.max_pending_settlements, channel balances,
and unresolved records through operator analytics/database access.
Symptom: settlement returns
settle_stellar_sponsor_budget_exceeded.
Check:
limits.max_sponsored_stroops_per_key_per_day
limits.max_global_sponsored_stroops_per_day
- caller identity (
FACILITATOR_API_KEYS, or anonymous source IP)
- enforcing-simulation estimated fee.
Do not raise ceilings blindly. Measure legitimate enforcing costs for each
scheme, supported smart-account class, and allowed hook. Failed submitted
transactions still consume fee budget.
Transaction status is unknown
Symptom: response contains
settle_stellar_transaction_status_unknown and a non-empty transaction hash,
or MCP returns SETTLEMENT_UNKNOWN.
The exact envelope and hash were persisted before submission. Record the known
hash and payment identifier, then poll/resolve that settlement. Do not create a
new payment authorization. The facilitator recovery loop polls unresolved
records every 15 seconds and identical identifier retries resume the same
record.
Check limits.settle_poll_ms, limits.settle_timeout_ms, RPC availability, and
the returned transaction hash. UNKNOWN is not proof of failure.
Symptom: tools/list returns x402_search_resources and
x402_get_resource, but no paid tool.
This is the intended hosted/default state. signer.mode: none means discovery
only. GET /healthz reports payments: "disabled".
Use a real MCP client because Streamable HTTP requires initialization and a
session. From openx402/mcp-server/, after npm ci:
x402_call_resource is absent
Cause: no signer provider is configured. The tool is registered only when
signer.mode is env-secret, external, or encrypted-key.
env-secret is stdio-only. A non-stdio signer deployment also requires bearer
keys through http.api_keys_env. Pubnet remote execution additionally requires
explicit pubnet enablement and budget.store: postgres.
The current paid implementation supports the stock Stellar exact client only.
The interface accepts upto, but no reusable upstream Stellar upto client
exists; it fails closed instead of downgrading to exact.
MCP mcp.json is missing mcpServers
Symptom: the agent rejects an MCP configuration containing only a URL.
Use the required top-level object:
Clients requiring an explicit transport should select Streamable HTTP.
mcp:// URL is not connectable
Symptom: paid MCP execution rejects a cataloged logical mcp:// resource.
mcp:// names a tool inside an established MCP session; it does not identify a
network endpoint. Use a seller declaration whose resource.url is the actual
reachable HTTPS MCP endpoint. If a logical identifier is unavoidable, the
private MCP operator must map it to a reviewed HTTPS endpoint in
network_security.resolved_mcp_endpoints. The hosted discovery MCP does not
connect to or pay that resource.
Pubnet refuses startup
Symptom: facilitator or paid MCP exits before listening.
This is fail-closed behavior. Facilitator pubnet requires:
development_auto_fund: false;
fee_ceilings_calibrated: true with actually measured values;
- at least one
FACILITATOR_API_KEYS value;
- valid sponsor and configured channel secrets;
- the configured minimum balance for sponsor and every channel;
- an explicitly configured
upto contract;
catalog_security.require_https_origins: true;
catalog_security.allow_local_origins: false;
discovery.include_unverified: false.
The current implementation requires an upto contract even if the intended
use is exact-only. Configuration cannot prove that contract is audited.
Paid remote MCP pubnet additionally requires a signer, authenticated transport,
and PostgreSQL budget store. Do not bypass these checks. Pubnet still requires
SDF/x402 decisions, ABI freeze, audit, calibrated fees, TTL operations, and
published conformance evidence.
Last modified on August 3, 2026