{"openapi":"3.1.0","info":{"title":"MultiHopper Self-Service API","version":"1.0.0","description":"Public REST API for quotes, funding, transfers, webhooks, and usage reporting.\n\nImplementation notes:\n- Transfer funding uses the current abstraction architecture already present in the app.\n- Clients receive intermediate-wallet funding transactions, sign/send them, then confirm tx hashes through the API.\n- Native SOL tiering uses backend CoinGecko spot pricing.\n- Reward claims on this branch are a beta flat-fee claim flow, not the final abstraction settlement model."},"servers":[{"url":"/","description":"Current local or deployed origin"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"mh_live_* or mh_test_*"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"requestId":{"type":"string"}},"required":["code","message","requestId"]}},"required":["error"]},"Pagination":{"type":"object","properties":{"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"required":["total","limit","offset"]},"EstimateFeeRequest":{"type":"object","properties":{"tokenMint":{"type":"string","description":"Solana mint address. Use native SOL mint for SOL transfers.","example":"So11111111111111111111111111111111111111112"},"amountRaw":{"type":"string","description":"Integer string in base units.","example":"1000000"},"tokenDecimals":{"type":"integer","minimum":0,"maximum":18,"default":9,"example":9},"tokenPriceUsd":{"type":"number","description":"Optional pricing context used for tier selection when required by the selected asset.","example":1},"hops":{"type":"integer","minimum":3,"maximum":10,"example":3}},"required":["tokenMint","amountRaw"]},"FeeEstimate":{"type":"object","properties":{"tier":{"type":"string","example":"standard"},"percentFeeBps":{"type":"integer","example":5},"percentFeeRaw":{"type":"string","example":"500"},"flatFeeLamportsPerHop":{"type":"integer","example":6000},"totalFlatFeeLamports":{"type":"integer","example":18000},"integratorPercentShare":{"type":"string","example":"250"},"integratorFlatShare":{"type":"integer","example":9000},"usdEquivalent":{"type":"number","example":0.15,"description":"USD-equivalent amount used only for pricing tier selection. Settlement remains in native token units."},"isTestMode":{"type":"boolean","example":true},"tokens":{"type":"object","properties":{"amountRaw":{"type":"string","example":"1000000"},"feeRaw":{"type":"string","example":"500"},"netDeliveredRaw":{"type":"string","example":"999500"},"decimals":{"type":"integer","example":9}},"required":["amountRaw","feeRaw","netDeliveredRaw","decimals"]},"sol":{"type":"object","properties":{"requiredSolUpFrontLamports":{"type":"integer","example":8500000},"breakdown":{"type":"object","properties":{"routeInitRentLamports":{"type":"integer"},"orchestratorRentLamports":{"type":"integer"},"ataRentLamports":{"type":"integer"},"transactionFeesLamports":{"type":"integer"},"priorityFeesLamports":{"type":"integer"},"protocolFlatFeeLamports":{"type":"integer"}},"required":["routeInitRentLamports","orchestratorRentLamports","ataRentLamports","transactionFeesLamports","priorityFeesLamports","protocolFlatFeeLamports"]},"refundableLamports":{"type":"integer","example":4000000},"netCostLamports":{"type":"integer","example":4500000}},"required":["requiredSolUpFrontLamports","breakdown","refundableLamports","netCostLamports"]}},"required":["tier","percentFeeBps","percentFeeRaw","flatFeeLamportsPerHop","totalFlatFeeLamports","integratorPercentShare","integratorFlatShare","usdEquivalent","isTestMode","tokens","sol"]},"CreateTransferRequest":{"type":"object","properties":{"tokenMint":{"type":"string","example":"So11111111111111111111111111111111111111112"},"tokenSymbol":{"type":"string","example":"SOL"},"tokenDecimals":{"type":"integer","minimum":0,"maximum":18,"example":9},"amountRaw":{"type":"string","example":"1000000"},"amountTokens":{"type":"string","example":"0.001"},"sourceOwner":{"type":"string","description":"Pubkey of the wallet that signs the route-init bundle and pays for it (also the on-chain creator).","example":"D9VA5Yz6Y5e6zxdWLJ8pSc4SGrPMgsV1sU6DB1DGWKwa"},"recipientWallet":{"type":"string","example":"7wqP9wt5yWQ7Mzf1J8Q4J7gY9E1eP8x5NsQmY1rN2d3G"},"hops":{"type":"integer","minimum":3,"maximum":10,"example":3},"arrivalSeconds":{"type":"integer","minimum":60,"example":60},"externalId":{"type":"string","example":"order-123"},"tokenPriceUsd":{"type":"number","description":"Optional pricing context used for tier selection when required by the selected asset.","example":1}},"required":["tokenMint","amountRaw","amountTokens","sourceOwner","recipientWallet"]},"PreparedTxBundle":{"type":"object","description":"Resumable deployment bundle. /prepare probes on-chain state and only emits txs whose effects have NOT yet landed. Re-call /prepare after a partial broadcast to resume; null/empty fields are pieces already on-chain. Broadcast order: routeInitTxs → orchestratorInitTx → sessionInitTxs → keeperFundingTx (skip any null/empty).","properties":{"routeInitTxs":{"type":"array","description":"Pending route-init transactions. Empty when route already deployed (and all hops added + ALT cached). Each tx is partially signed by required ephemeral signers; the user wallet only needs to add its own signature.","items":{"type":"object","properties":{"base64":{"type":"string","description":"base64-encoded VersionedTransaction."}},"required":["base64"]}},"orchestratorInitTx":{"type":"string","nullable":true,"description":"base64-encoded unsigned orchestrator-init transaction. Null when the orchestrator config PDA is already on-chain."},"sessionInitTxs":{"type":"array","items":{"type":"string"},"description":"Pending session-init transactions (init_step × N + ATA pre-init). Drops entries whose step_state PDAs already exist on-chain."},"keeperFundingTx":{"type":"string","nullable":true,"description":"Standalone keeper-funding tx. Null when the keeper already holds enough lamports for this route's execution."},"recentBlockhash":{"type":"string"},"lastValidBlockHeight":{"type":"integer"},"resume":{"type":"object","description":"On-chain progress snapshot used to decide which pieces above were skipped. Lets clients render a precise resume-from-here UI.","properties":{"routeAlreadyDeployed":{"type":"boolean"},"existingHopCount":{"type":"integer"},"totalHops":{"type":"integer"},"orchestratorAlreadyInitialized":{"type":"boolean"},"completedStepIndices":{"type":"array","items":{"type":"integer"}},"totalSteps":{"type":"integer"},"keeperAlreadyFunded":{"type":"boolean"},"nothingToDo":{"type":"boolean","description":"True when every bundle is empty — deployment is fully on-chain."}},"required":["routeAlreadyDeployed","existingHopCount","totalHops","orchestratorAlreadyInitialized","completedStepIndices","totalSteps","keeperAlreadyFunded","nothingToDo"]}},"required":["routeInitTxs","orchestratorInitTx","sessionInitTxs","keeperFundingTx","recentBlockhash","lastValidBlockHeight","resume"]},"Transfer":{"type":"object","properties":{"id":{"type":"integer","example":42},"externalId":{"type":"string","nullable":true,"example":"order-123"},"supportBundleId":{"type":"string","nullable":true,"example":"sb_7g8h9i"},"tokenMint":{"type":"string","example":"So11111111111111111111111111111111111111112"},"tokenSymbol":{"type":"string","nullable":true,"example":"SOL"},"amountRaw":{"type":"string","example":"1000000"},"amountTokens":{"type":"string","example":"0.001"},"sourceOwner":{"type":"string","example":"D9VA5Yz6Y5e6zxdWLJ8pSc4SGrPMgsV1sU6DB1DGWKwa"},"recipientWallet":{"type":"string","example":"7wqP9wt5yWQ7Mzf1J8Q4J7gY9E1eP8x5NsQmY1rN2d3G"},"fundingStrategy":{"type":"string","nullable":true,"example":"direct_orchestration"},"hops":{"type":"integer","example":3},"arrivalSeconds":{"type":"integer","example":60},"pricingTier":{"type":"string","nullable":true,"example":"standard"},"percentFeeBps":{"type":"integer","nullable":true,"example":5},"totalFlatFeeLamports":{"type":"integer","nullable":true,"example":18000},"status":{"type":"string","enum":["quote","awaiting_signature","processing","completed","failed","expired","refunded"],"example":"awaiting_signature"},"isTest":{"type":"boolean","example":true},"quotedAt":{"type":"string","format":"date-time","nullable":true},"depositReceivedAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"phase":{"type":"string","enum":["quoted","funding","deploying","executing","settled","failed","recoverable","expired","rescued","reclaimed"],"example":"executing"},"progress":{"type":"object","properties":{"hopsCompleted":{"type":"integer","example":2},"hopsTotal":{"type":"integer","example":5}},"required":["hopsCompleted","hopsTotal"]},"lastError":{"type":"object","nullable":true,"properties":{"code":{"type":"string","example":"MH_042"},"message":{"type":"string"},"at":{"type":"string","format":"date-time"}},"required":["code","message","at"]},"recovery":{"type":"object","nullable":true,"description":"Populated when phase is in {failed, recoverable, settled, rescued}; null otherwise.","properties":{"canRescue":{"type":"boolean"},"rescuableLamports":{"type":"integer"},"rescuableAccounts":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["route_pda","step_state","orchestrator_config","keeper","ata"]},"lamports":{"type":"integer"}},"required":["kind","lamports"]}},"canReclaimRent":{"type":"boolean"},"reclaimableLamports":{"type":"integer"}},"required":["canRescue","rescuableLamports","rescuableAccounts","canReclaimRent","reclaimableLamports"]},"signatures":{"type":"object","properties":{"routeInit":{"type":"array","items":{"type":"string"}},"sessionInit":{"type":"array","items":{"type":"string"}},"hops":{"type":"array","items":{"type":"string"}}},"required":["routeInit","sessionInit","hops"]}},"required":["id","tokenMint","amountRaw","amountTokens","sourceOwner","recipientWallet","hops","arrivalSeconds","status","isTest","createdAt"]},"RescueBundle":{"type":"object","properties":{"rescueTxs":{"type":"array","items":{"type":"object","properties":{"base64":{"type":"string"},"partialSigners":{"type":"array","items":{"type":"string"}}},"required":["base64","partialSigners"]}},"recentBlockhash":{"type":"string"},"lastValidBlockHeight":{"type":"integer"},"rescuable":{"type":"object","properties":{"totalLamports":{"type":"integer"},"accounts":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"lamports":{"type":"integer"}},"required":["kind","lamports"]}}},"required":["totalLamports","accounts"]}},"required":["rescueTxs","recentBlockhash","lastValidBlockHeight","rescuable"]},"ReclaimBundle":{"type":"object","properties":{"reclaimTxs":{"type":"array","items":{"type":"object","properties":{"base64":{"type":"string"},"partialSigners":{"type":"array","items":{"type":"string"}}},"required":["base64","partialSigners"]}},"recentBlockhash":{"type":"string"},"lastValidBlockHeight":{"type":"integer"},"reclaimable":{"type":"object","properties":{"totalLamports":{"type":"integer"},"stepCount":{"type":"integer"}},"required":["totalLamports","stepCount"]}},"required":["reclaimTxs","recentBlockhash","lastValidBlockHeight","reclaimable"]},"TransferList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Transfer"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"]},"WebhookCreateRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","example":"https://example.com/multihopper/webhooks"},"events":{"type":"array","items":{"type":"string","enum":["transfer.quote_created","transfer.deposit_confirmed","transfer.processing","transfer.hop_complete","transfer.completed","transfer.failed","transfer.expired","transfer.refunded","transfer.phase_changed","transfer.recoverable","transfer.rescued","transfer.rent_reclaimed"]},"example":["transfer.completed","transfer.failed"]}},"required":["url"]},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"integer","example":1},"url":{"type":"string","format":"uri","example":"https://example.com/multihopper/webhooks"},"secret":{"type":"string","nullable":true,"example":"whsec_1234567890abcdef"},"events":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"isActive":{"type":"boolean","example":true},"createdAt":{"type":"string","format":"date-time"}},"required":["id","url","isActive","createdAt"]},"WebhookEndpointList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}},"required":["items"]},"UsageSummary":{"type":"object","description":"Integration usage and revenue summary.","additionalProperties":true,"example":{"totalTransfers":3,"completedTransfers":2,"failedTransfers":1,"totalVolumeUsd":154.23,"totalRevenueLamports":24000,"isTestMode":true}}}},"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"paths":{"/api/v1/transfers/estimate":{"post":{"summary":"Estimate transfer fees","description":"Uses USD-equivalent pricing only to select the commercial tier. Native SOL uses backend CoinGecko spot pricing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EstimateFeeRequest"}}}},"responses":{"200":{"description":"Fee estimate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeeEstimate"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/transfers":{"post":{"summary":"Create a transfer and return abstraction funding transactions","description":"Creates the transfer, internal route, and current abstraction funding bundle. Clients sign and send the returned intermediate-wallet funding transactions, then confirm the submitted transaction hashes through the funding confirmation endpoint.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTransferRequest"}}}},"responses":{"200":{"description":"Transfer created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transfer"}}}}}},"get":{"summary":"List transfers","parameters":[{"name":"status","in":"query","schema":{"type":"string"}},{"name":"fromDate","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"toDate","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"externalId","in":"query","schema":{"type":"string"}},{"name":"supportBundleId","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Transfer list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferList"}}}}}}},"/api/v1/transfers/{transferId}":{"get":{"summary":"Get a transfer by id","parameters":[{"name":"transferId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Transfer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transfer"}}}},"404":{"description":"Transfer not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/transfers/by-external/{externalId}":{"get":{"summary":"Get a transfer by external id","parameters":[{"name":"externalId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Transfer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transfer"}}}}}}},"/api/v1/webhooks":{"post":{"summary":"Register a webhook endpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateRequest"}}}},"responses":{"200":{"description":"Webhook created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}},"get":{"summary":"List webhook endpoints","responses":{"200":{"description":"Webhook list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}}}}},"/api/v1/webhooks/{endpointId}":{"delete":{"summary":"Delete a webhook endpoint","parameters":[{"name":"endpointId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Webhook deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","example":true}},"required":["deleted"]}}}}}}},"/api/v1/transfers/{transferId}/rescue/prepare":{"post":{"summary":"Prepare rescue txs (catastrophic recovery)","description":"Builds unsigned rescue transactions when a transfer has failed and funds are locked in PDAs. Allowed only when phase is 'failed' or 'recoverable'.","parameters":[{"name":"transferId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Rescue bundle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RescueBundle"}}}},"409":{"description":"MH_080 / MH_081"}}}},"/api/v1/transfers/{transferId}/rescue/confirm":{"post":{"summary":"Confirm rescue txs","parameters":[{"name":"transferId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rescueSignatures":{"type":"array","items":{"type":"string"},"minItems":1}},"required":["rescueSignatures"]}}}},"responses":{"200":{"description":"Updated transfer","content":{"application/json":{"schema":{"type":"object","properties":{"transfer":{"$ref":"#/components/schemas/Transfer"}},"required":["transfer"]}}}}}}},"/api/v1/transfers/{transferId}/reclaim-rent/prepare":{"post":{"summary":"Prepare reclaim-rent txs (post-completion cleanup)","parameters":[{"name":"transferId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Reclaim bundle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReclaimBundle"}}}},"409":{"description":"MH_080 / MH_082"}}}},"/api/v1/transfers/{transferId}/reclaim-rent/confirm":{"post":{"summary":"Confirm reclaim-rent txs","parameters":[{"name":"transferId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reclaimSignatures":{"type":"array","items":{"type":"string"},"minItems":1}},"required":["reclaimSignatures"]}}}},"responses":{"200":{"description":"Per-signature confirmation result","content":{"application/json":{"schema":{"type":"object","properties":{"confirmed":{"type":"array","items":{"type":"string"}},"failed":{"type":"array","items":{"type":"object","properties":{"signature":{"type":"string"},"error":{"type":"string"}},"required":["signature","error"]}}},"required":["confirmed","failed"]}}}}}}},"/api/v1/usage":{"get":{"summary":"Get usage summary","parameters":[{"name":"fromDate","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"toDate","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Usage summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageSummary"}}}}}}}}}