Book an India visa from your own agent
A remote MCP server that exposes Naytive's Visit India / Edge City visa flow â search visas, upload a passport & photo, file the application, and take payment â as tools any MCP-compatible AI agent can call on a user's behalf.
The same server also forms Próspera ZEDE companies (Honduras, via DAZ) for your users — see Próspera companies.
Overview
The server speaks the Model Context Protocol over Streamable HTTP. Point any MCP client at the endpoint above; the client discovers the tools and drives them. Every call acts on behalf of a signed-in Naytive user, so the visa is filed against a real account with a real document vault.
Transport
Streamable HTTP (JSON-RPC 2.0). One endpoint: POST /mcp.
Auth
Two keys: a Naytive partner API key (X-API-Key, issued to you) identifies your integration; the traveller signs in with Privy (OAuth 2.1 + PKCE) in the browser.
Billing
You charge your own users through your own Stripe. Naytive's per-application fee is drawn from a prepaid wallet you top up.
Get an API key
Every call carries a Naytive partner API key in the X-API-Key header, so all usage is attributed to your integration and billed to your wallet. Keys are issued by Naytive â request one below and we'll approve it and send your key (shown once).
1 · Request
Send your organisation and a contact email. We review and approve.
2 · Receive your key
On approval you get an oc_live_⦠/ oc_test_⦠key. Store it as a secret.
3 · Top up & build
Add funds to your wallet, then point your MCP client at the endpoint with your key set.
Connect a client
The endpoint is a standard remote MCP server, so anything that speaks MCP works. On first use the client opens a browser window for the user to sign in, then remembers the session.
Claude Desktop / Claude Code
Add the server to your MCP config (Claude Code: run the CLI command; Desktop: edit claude_desktop_config.json). Set your partner key as the X-API-Key header. Optionally add X-Stripe-Key (a restricted Stripe key) to collect end-user payments on your own Stripe â see Wallet & billing.
// mcpServers entry { "mcpServers": { "naytive-visa": { "type": "http", "url": "https://visa-mcp.staging.naytive.com/mcp", "headers": { "X-API-Key": "oc_test_your_key_here", // optional â collect on your own Stripe: "X-Stripe-Key": "rk_test_your_restricted_key" } } } }
claude mcp add --transport http naytive-visa https://visa-mcp.staging.naytive.com/mcp \ --header "X-API-Key: oc_test_your_key_here"
Cursor
Add to ~/.cursor/mcp.json (or the project's .cursor/mcp.json) with the same shape â "url" pointing at the endpoint and an X-API-Key header.
MCP Inspector
The quickest way to click through the tools. Use v2+, which supports the OAuth sign-in:
npx @modelcontextprotocol/inspector@latest
In the UI: Add Server â Transport Streamable HTTP â URL the endpoint â toggle it on â complete the Naytive sign-in.
Custom client (TypeScript SDK)
import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const transport = new StreamableHTTPClientTransport( new URL("https://visa-mcp.staging.naytive.com/mcp"), { authProvider } // your OAuthClientProvider â SDK runs the PKCE flow ); const client = new Client({ name: "my-agent", version: "1.0.0" }, { capabilities: {} }); await client.connect(transport); const { tools } = await client.listTools(); const res = await client.callTool({ name: "list_india_events", arguments: {} });
Try it
Run any tool live, right here. Every call needs your X-API-Key. Wallet & catalog tools run with just the key; traveller tools also need a Bearer access token.
// the response will appear here
Tip: run list_india_events first to get a visaConfigId, then paste it into get_visa_details.
Authentication
Two layers. Your partner API key (X-API-Key) identifies your integration on every request and is what your wallet is billed against. On top of that, user-scoped tools need the traveller's Privy sign-in (OAuth 2.1). A request with no key returns 401; a user tool with no sign-in returns 401 + WWW-Authenticate, which a compliant client uses to run the browser sign-in automatically.
- Set your Naytive-issued key as the
X-API-Keyheader (see Get an API key). It never expires unless revoked. - For a user-scoped tool, the client opens the authorize URL; the server redirects to Naytive's hosted sign-in, where the traveller logs in with email or wallet (Privy).
- Naytive verifies the identity and the server issues a short-lived access token bound to that user. The client sends it as
Authorization: Bearer â¦alongside yourX-API-Key.
X-API-Key = your integration (billing + attribution); the Privy sign-in = the traveller the visa is filed under, using that user's stored documents. Access tokens are short-lived and refresh automatically.{
"issuer": "https://visa-mcp.staging.naytive.com/",
"authorization_endpoint": ".../authorize",
"token_endpoint": ".../token",
"registration_endpoint": ".../register",
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "refresh_token"]
}
Wallet & billing
Two ways to collect payment from your end-user â you choose per connection with the optional X-Stripe-Key header:
Your own Stripe
Set an X-Stripe-Key header (a restricted Stripe key). The end-user's checkout is created on your Stripe â they pay you. Naytive's fee is drawn from your prepaid wallet and the visa is filed immediately; submit_application returns a paymentUrl for your end-user.
Naytive collects (default)
No X-Stripe-Key â the end-user pays Naytive. submit_application creates a pending_payment; call create_payment_link for a Naytive checkout. No wallet is used.
Top up & rate
Own-Stripe mode debits fee à traveller count per filing. Check balance + rate with get_wallet_balance; add funds with create_wallet_topup_link. Low balance â the application is not created (402).
X-Stripe-Key travels as a request header (not a tool argument, so it never enters the model's context), is used just-in-time to create the checkout on your account, and is never logged or persisted. Use a restricted key. Wallet debits are atomic and idempotent â a retried submit is charged at most once and the balance can never go negative.Tools
Twelve visa tools, grouped by what they touch (plus seven Próspera company tools). Read tools return catalog, account, or wallet data; write tools mutate the user's vault, applications, or your wallet. Every tool returns a JSON payload as text content. All tools require your X-API-Key; user-scoped tools also need the traveller's sign-in.
visaConfigId comes from list_india_events; an applicationId / applicationNumber comes from submit_application or list_applications; document and photo ids come from the upload tools. Treat them as opaque handles.Catalog
list_india_events
ReadThe India builder events you can get a visa for (Edge City India, India Blockchain Week, Devcon Mumbai) and the available India visa options. No arguments.
get_visa_details
ReadFull detail for one visa: validity, max stay, entries, processing time, nationality-aware pricing, and the required documents.
| Field | Type | Notes | |
|---|---|---|---|
| visaConfigId | integer | required | From list_india_events. |
| nationality | string | optional | ISO code (US, GB, IN) for accurate pricing/docs. |
get_required_documents
ReadRequired documents for a visa, merged with what the signed-in user already has stored â so the agent only asks for what's missing.
| Field | Type | Notes | |
|---|---|---|---|
| visaConfigId | integer | required | The handle returned by list_india_events. |
| nationality | string | optional | ISO code. |
Applications
list_applications
ReadThe signed-in user's filed visa applications with current status, travel dates, and amount. No arguments.
check_application_status
ReadStatus and detail for one application by its number.
| Field | Type | Notes | |
|---|---|---|---|
| applicationNumber | string | required | e.g. NV⦠|
Documents
upload_passport
WriteUpload a passport image or PDF. It's read, the identity fields extracted, and stored in the traveller's vault. Returns a documentId to pass to submit_application.
| Field | Type | Notes | |
|---|---|---|---|
| fileBase64 | string | required | Raw base64 (no data: prefix), ⤠6 MB. |
| fileName | string | required | |
| fileMime | string | required | image/png, image/jpeg, or application/pdf. |
upload_photo
WriteUpload the traveller's passport-size photo. Returns a photoId for submit_application.
| Field | Type | Notes | |
|---|---|---|---|
| imageBase64 | string | required | Base64 JPEG/PNG. |
| countryCode | string | optional | Destination code, default IN. |
| format | string | optional | jpeg / png. |
Submit & pay
submit_application
WriteFile the application. Every traveller must already have a passport documentId and a photoId, or the call is rejected. Naytive's fee (rate à travellers) is debited from your wallet and the application is filed immediately (submitted). If the balance is too low nothing is created â top up and retry. You collect payment from your own end-user separately.
| Field | Type | Notes | |
|---|---|---|---|
| visaConfigId | integer | required | The handle returned by list_india_events. |
| nationality | string | optional | ISO code. |
| arrivalDate / departureDate | string | optional | YYYY-MM-DD. |
| eventName / visitType | string | optional | e.g. "Edge City India". |
| documentIds | integer[] | single | Passport doc id(s) for a solo applicant. |
| processedPhotoId | integer | single | Photo id for a solo applicant. |
| travelers | object[] | group | One per traveller: { fullName?, relationship?, documentIds, processedPhotoId }. |
create_payment_link
WriteAlternate flow. Have Naytive collect from the traveller directly (Stripe/Helio) instead of your wallet. Not needed for the wallet model above â a wallet-billed application is already submitted.
| Field | Type | Notes | |
|---|---|---|---|
| applicationId | integer | required | From submit_application. |
| paymentMode | string | optional | card (default) or crypto. |
| returnUrl | string | optional | Where to send the browser after payment. Pass your own app URL (e.g. your success page) — if omitted it defaults to naytive.com. |
Wallet & billing
get_wallet_balance
ReadYour prepaid wallet balance (USD cents), currency, and the per-application wholesale fee. No arguments.
list_wallet_transactions
ReadRecent ledger entries â top-ups, per-application debits, and refunds â each with the running balance.
| Field | Type | Notes | |
|---|---|---|---|
| limit | integer | optional | 1â100, default 20. |
create_wallet_topup_link
WriteA Stripe checkout url to add funds to your wallet. Open it in a browser to pay; the balance updates automatically once the charge settles.
| Field | Type | Notes | |
|---|---|---|---|
| amountUsd | number | required | Amount to add in USD (e.g. 100 = $100). Minimum $1. |
| returnUrl | string | optional | Where to send the browser after payment. Pass your own app URL (e.g. your success page) — if omitted it defaults to naytive.com. |
Filing a visa, end to end
A typical agent conversation walks the user from "what can I get" to a filed, paid application. The server owns the rules; the agent just calls tools in order.
Discover
list_india_eventsâ pick a visa and event.get_visa_detailsfor pricing and the document checklist.Check the vault
get_required_documentsshows what the user already has stored versus what's still needed.Collect documents
upload_passportandupload_photofor anything missing. Keep the returneddocumentIdandphotoId.Fund your wallet
Once, up front:
get_wallet_balanceto check funds;create_wallet_topup_linkif you need more. Your end-user pays you separately, through your own Stripe.File
submit_applicationwith the ids, dates, and event. Naytive's fee is debited from your wallet and the application is filed â it returns anapplicationNumberalreadysubmitted.Track
list_applications/check_application_statusâ the application readssubmittedand moves through review.
Próspera companies
Form a Próspera ZEDE LLC (Roatán, Honduras) for your user, end to end, through Naytive's DAZ partnership. Same auth as visas: your X-API-Key on every call, and the user's sign-in (Bearer) for anything that creates or reads their companies.
Fee
Government fee $500 + Naytive service fee $200 = $700 per company, debited from your prepaid wallet when the company is submitted. Charge your user any retail price you like. Check it live with get_prospera_pricing.
Signature
create_prospera_company usually returns a signatureUrl. Send the founder there to sign the incorporation documents digitally. Nothing progresses until they do.
Status
Próspera has no push. Naytive polls every 15 minutes, and check_prospera_company_status asks Próspera live. Review typically takes about 14 days.
create_prospera_company sends the company to Próspera immediately and charges your wallet. Confirm every detail with the user first.refund row appears in list_wallet_transactions, with ref_business_registration_id). Once Próspera has accepted a company, a later rejection or archival is not refunded automatically — contact Naytive; refunds are a manual decision.status: "reconciling" — "Submitted; confirming with Próspera — check status shortly". Do not call create again. Naytive looks it up on Próspera: found → it continues normally (usually pending_signature); no record after about 30 minutes → cancelled and refunded automatically.Forming a company, end to end
Discover
list_prospera_optionsreturns the jurisdiction, fee, activity codes, chains andrequiredFields. If it saysconnected:false, creation is not live yet.Collect
Ask the founder for the required fields: company legal and trading name, main activity, founder name, email and role, and a principal office address. DAZ supports one founder, who is the sole shareholder.
Check funds
get_prospera_pricingshows the fee, your balance andcanAfford. Top up withcreate_wallet_topup_linkif needed.Create
create_prospera_company. The fee is debited and the company is submitted; you get aregistrationId, astatusand usually asignatureUrl.Sign
The founder opens
signatureUrland signs. Próspera may then email them for KYC (pending_kyb).Track
check_prospera_company_statusuntilactive(orrejected).list_prospera_companieslists them all.Documents
Once
active:list_prospera_documents, thendownload_prospera_documentfor the certificate and the other corporate documents.
Tools
list_prospera_options
ReadJurisdictions with fees, business activity codes, chains (POLYGON, ETHEREUM), every field to collect (requiredFields), the steps, and connected. Key only; no arguments.
get_prospera_pricing
ReadGovernment + service fee in USD and cents, your wallet balance, and canAfford. Key only.
| Field | Type | Notes | |
|---|---|---|---|
| jurisdictionId | integer | optional | Defaults to Próspera ZEDE. |
create_prospera_company
WriteSubmits the company to Próspera and debits the fee from your wallet. Returns registrationId, registrationNumber, status, signatureUrl, nextStep and billing. Needs the user's sign-in.
| Field | Type | Notes | |
|---|---|---|---|
| legalName | string | required | Full legal name, e.g. "Acme Corp LLC". |
| tradingName | string | required | Trading name, e.g. "Acme". |
| mainActivity | string | required | An activity code from list_prospera_options. |
| secondaryActivities | string[] | optional | Up to 10 more activity codes. |
| chain | string | optional | POLYGON (default) or ETHEREUM, any case. |
| founder | object | required | { name, email, jobRole?, kycProviderId? }. jobRole defaults to Director. Próspera emails the founder. |
| principalOffice | object | required | { line1, line2?, city, state?, postalCode, country }; country is a 2-letter ISO code, e.g. HN. |
| jurisdictionId | integer | optional | Defaults to Próspera ZEDE. |
list_prospera_companies
ReadThe user's Próspera companies with last known status, signatureUrl while signing is pending, and nextStep. No arguments.
check_prospera_company_status
ReadLive status from Próspera, plus prosperaApplicationStatus and, once active, zoneRegistrationNumber.
| Field | Type | Notes | |
|---|---|---|---|
| registrationId | integer | required | From create_prospera_company or list_prospera_companies. |
list_prospera_documents
ReadDocument types Próspera offers for the company (e.g. certificate, terms, background_check, civil_penalty, agreement_of_coexistence); downloadable once active.
| Field | Type | Notes | |
|---|---|---|---|
| registrationId | integer | required |
download_prospera_document
ReadOne document (usually a PDF) as an embedded resource (base64 blob), with its name, type and size.
| Field | Type | Notes | |
|---|---|---|---|
| registrationId | integer | required | |
| documentType | string | required | From list_prospera_documents. |
Statuses
| status | Meaning |
|---|---|
| reconciling | Submitted, but Próspera did not answer in time; Naytive is confirming it. Check again in a few minutes; do not resubmit. |
| pending_signature | The founder must sign at signatureUrl. |
| processing | Próspera is processing (includes Próspera's own payment step). |
| pending_kyb | KYC/KYB checks on the founder. |
| pending_review | Under review by Próspera (about 14 days). |
| waiting_activate | Approved, awaiting activation. |
| active | Formed. Documents are downloadable. |
| rejected | Próspera rejected the application after accepting it. Not refunded automatically — contact Naytive. |
| cancelled | Either Próspera refused or never received the submission (paymentStatus: "refunded", refunded automatically), or Próspera later archived/deleted the company (not refunded automatically). nextStep says which. |
Errors
Próspera tool errors are JSON in the text content (isError: true) with an error code to branch on:
| error | What happened |
|---|---|
| prospera_not_connected | Naytive's Próspera connection is not live yet. Nothing was created or charged. Catalog and pricing still work. |
| validation_failed | Missing or invalid details. errors lists each { field, message }; ask the user and retry. Nothing charged. |
| insufficient_wallet_balance | feeCents and balanceCents included. Nothing created. Top up and retry. |
| duplicate_registration | The user already has this company (same legal name) in progress. Nothing charged; use the returned registrationId. |
| prospera_rejected | Próspera refused the submission (providerMessage says why). The fee was refunded in full. |
| prospera_auth_failed | Próspera refused Naytive's credentials. Nothing was created; the fee was refunded in full. Contact Naytive. |
| not_found | No company with that registrationId for this user under your partner key. |
| document_not_available | The company is not active yet, or Próspera has no such document. |
Errors & limits
Tool errors come back as text content with isError: true and a plain message. A few things worth knowing before you build against it.
| Situation | What you get |
|---|---|
| Missing / invalid API key | 401 â set your Naytive X-API-Key header. |
| No / expired user token | 401 + WWW-Authenticate â the client re-runs sign-in. |
| Wallet balance too low on submit | 402 insufficient_wallet_balance with the fee and current balance â no application is created. Top up and retry. |
| Missing passport or photo on submit | Error: "Passport and photo are required for every traveller before filing." |
| File over 6 MB | Error: file too large. |
- Wallet debits are atomic & idempotent. A submitted application is charged once; a retry is never double-charged and the balance can't go negative. Top-ups are credited by the Stripe webhook, not from the client claiming success.
- Documents belong to the signed-in user. A tool can only reference documents and applications owned by the authenticated account.
- Staging Stripe is in test mode â use a test card at checkout.