{"openapi":"3.1.0","info":{"title":"Genko Crypto API","version":"0.1.0","description":"PayFi API for USDC deposits, Superfluid payment streams, and Aave yield on Base (chain ID 8453). Non-custodial: operator holds flow permissions only, never custody of funds.","contact":{"email":"legal@getgenko.com"}},"servers":[{"url":"https://crypto.getgenko.com","description":"Production"}],"components":{"securitySchemes":{"bearerJwt":{"type":"http","scheme":"bearer","description":"JWT obtained from GET /auth/callback"}},"schemas":{"Error":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"}},"required":["message","code"]}}},"EthAddress":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"EIP-55 checksummed Ethereum address","example":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"},"TxHash":{"type":"string","pattern":"^0x[0-9a-fA-F]{64}$","description":"32-byte transaction hash"}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Unique key per logical operation. Re-sending the same key returns the cached result without re-executing."}}},"paths":{"/health":{"get":{"summary":"Health check","operationId":"getHealth","tags":["System"],"responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"db":{"type":"boolean"},"dbError":{"type":"string"},"ts":{"type":"string","format":"date-time"}}},"error":{"type":"null"}}}}}},"503":{"description":"Database unreachable"}}}},"/auth/signup":{"post":{"summary":"Create account and send magic link","operationId":"signup","tags":["Auth"],"x-rate-limit":"5 req/min per IP","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","full_name"],"properties":{"email":{"type":"string","format":"email"},"full_name":{"type":"string","minLength":1,"maxLength":100},"company":{"type":"string","maxLength":100}}}}}},"responses":{"201":{"description":"Account created. Magic link sent to email.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"},"dev_token":{"type":"string","description":"Only present when NODE_ENV !== production"}}},"error":{"type":"null"}}}}}},"409":{"description":"Email already registered (EMAIL_EXISTS)","$ref":"#/components/schemas/Error"}}}},"/auth/magic-link":{"post":{"summary":"Request a new sign-in link for an existing account","operationId":"requestMagicLink","tags":["Auth"],"x-rate-limit":"5 req/min per IP","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Magic link sent (response is identical whether email exists or not)"}}}},"/auth/callback":{"get":{"summary":"Exchange magic-link token for a JWT","operationId":"authCallback","tags":["Auth"],"parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"JWT issued. Valid for 7 days.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"access_token":{"type":"string"},"expires_in":{"type":"integer","example":604800}}},"error":{"type":"null"}}}}}},"401":{"description":"Token expired or already used (INVALID_LINK)"}}}},"/auth/me":{"get":{"summary":"Get the authenticated account","operationId":"getMe","tags":["Auth"],"security":[{"bearerJwt":[]}],"responses":{"200":{"description":"Account details","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Account"},"error":{"type":"null"}}}}}}}}},"/auth/logout":{"post":{"summary":"Revoke the current JWT","operationId":"logout","tags":["Auth"],"security":[{"bearerJwt":[]}],"responses":{"200":{"description":"Token revoked. Subsequent requests with this JWT will be rejected."}}}},"/account":{"get":{"summary":"Get account details and registered wallets","operationId":"getAccount","tags":["Account"],"security":[{"bearerJwt":[]}],"responses":{"200":{"description":"Account object"}}}},"/account/usage":{"get":{"summary":"Get operation counts by type","operationId":"getUsage","tags":["Account"],"security":[{"bearerJwt":[]}],"responses":{"200":{"description":"Counts per operation type","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":{"type":"integer"},"example":{"stream_start":14,"stream_stop":14,"deposit_verify":8}}}}}}}}}},"/account/wallets":{"post":{"summary":"Register a wallet address to this account","operationId":"addWallet","tags":["Account"],"security":[{"bearerJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"$ref":"#/components/schemas/EthAddress"},"label":{"type":"string","maxLength":80},"is_primary":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Wallet registered"},"409":{"description":"Address already registered (WALLET_EXISTS)"}}}},"/account/wallets/{id}":{"delete":{"summary":"Remove a registered wallet","operationId":"deleteWallet","tags":["Account"],"security":[{"bearerJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ id, deleted: true }"},"404":{"description":"Wallet not found"}}}},"/onboarding/operator-calldata":{"get":{"summary":"Get calldata to grant the operator flow permissions","operationId":"getOperatorCalldata","tags":["Onboarding"],"security":[{"bearerJwt":[]}],"description":"Returns a ready-to-sign transaction that calls `updateFlowOperatorPermissions` on the Superfluid CFA Forwarder with permission bits 7 (create + update + delete). The practitioner submits this transaction from their wallet.","responses":{"200":{"description":"Transaction request object","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"tx":{"type":"object","properties":{"to":{"$ref":"#/components/schemas/EthAddress"},"data":{"type":"string"},"chainId":{"type":"integer","example":8453}}}}}}}}}}}}},"/onboarding/verify-operator":{"post":{"summary":"Verify that the operator grant transaction landed on-chain","operationId":"verifyOperator","tags":["Onboarding"],"security":[{"bearerJwt":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["walletAddress"],"properties":{"walletAddress":{"$ref":"#/components/schemas/EthAddress"}}}}}},"responses":{"200":{"description":"Operator grant confirmed on-chain"},"400":{"description":"Grant not found on-chain or wallet not registered (VERIFY_OPERATOR_FAILED)"}}}},"/onboarding/wrap-calldata":{"get":{"summary":"Get calldata to approve and wrap USDC → USDCx","operationId":"getWrapCalldata","tags":["Onboarding"],"security":[{"bearerJwt":[]}],"description":"Returns two sequential transactions: (1) ERC-20 approve USDC → Superfluid wrapper, (2) upgrade (wrap) USDC to USDCx. USDCx is the streaming token required for Superfluid flows.","parameters":[{"name":"amount","in":"query","required":true,"schema":{"type":"integer","description":"Amount in USDC atomic units (6 decimals). E.g. 10000000 = 10 USDC."}}],"responses":{"200":{"description":"Two transaction objects","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"transactions":{"type":"array","items":{"type":"object"},"minItems":2,"maxItems":2}}}}}}}}}}},"/onboarding/verify-wrap":{"post":{"summary":"Verify that the USDC wrap transaction landed on-chain","operationId":"verifyWrap","tags":["Onboarding"],"security":[{"bearerJwt":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["walletAddress","requiredAmountUsdc6"],"properties":{"walletAddress":{"$ref":"#/components/schemas/EthAddress"},"requiredAmountUsdc6":{"type":"string","pattern":"^[0-9]+$","description":"Minimum USDCx balance expected, in 6-decimal atomic units"}}}}}},"responses":{"200":{"description":"Wrap confirmed, wallet has sufficient USDCx balance"},"400":{"description":"Wrap not found or balance insufficient (VERIFY_WRAP_FAILED)"}}}},"/streams/start":{"post":{"summary":"Start a Superfluid payment stream","operationId":"startStream","tags":["Streams"],"security":[{"bearerJwt":[]}],"x-rate-limit":"20 req/min per account","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"description":"Creates or updates a Superfluid CFA flow from practitionerAddress → receiverAddress at the given rate. The operator wallet submits the transaction on behalf of the practitioner. Prerequisite: operator grant + USDCx balance must both be in place.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["practitionerAddress","receiverAddress","ratePerMin","sessionId"],"properties":{"practitionerAddress":{"$ref":"#/components/schemas/EthAddress"},"receiverAddress":{"$ref":"#/components/schemas/EthAddress"},"ratePerMin":{"type":"number","minimum":0,"description":"USDC per minute (e.g. 1.5 = $1.50/min)"},"sessionId":{"type":"string","format":"uuid","description":"Your booking/session ID"}}}}}},"responses":{"200":{"description":"Stream started","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"txHash":{"$ref":"#/components/schemas/TxHash"},"flowRate":{"type":"string","description":"On-chain flow rate in wei/second"},"sessionId":{"type":"string","format":"uuid"}}}}}}}},"400":{"description":"Transaction failed or operator permissions missing (STREAM_START_FAILED)"}}}},"/streams/stop":{"post":{"summary":"Stop an active payment stream","operationId":"stopStream","tags":["Streams"],"security":[{"bearerJwt":[]}],"x-rate-limit":"20 req/min per account","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sessionId"],"properties":{"sessionId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Stream stopped","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"txHash":{"$ref":"#/components/schemas/TxHash"},"sessionId":{"type":"string","format":"uuid"}}}}}}}}}}},"/streams/{sessionId}":{"get":{"summary":"Get stream status","operationId":"getStream","tags":["Streams"],"security":[{"bearerJwt":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Stream record with status field"},"404":{"description":"Session not found (STREAM_NOT_FOUND)"}}}},"/deposits/verify":{"post":{"summary":"Verify a USDC deposit transaction on-chain","operationId":"verifyDeposit","tags":["Deposits"],"security":[{"bearerJwt":[]}],"x-rate-limit":"20 req/min per account","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"description":"Confirms a USDC transfer landed on Base and credits it to the account. `expectedRecipient` must be one of the account's registered wallet addresses.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["txHash","expectedRecipient"],"properties":{"txHash":{"$ref":"#/components/schemas/TxHash"},"expectedRecipient":{"$ref":"#/components/schemas/EthAddress"}}}}}},"responses":{"200":{"description":"Deposit verified and recorded"},"400":{"description":"Transaction not found or amount/recipient mismatch (DEPOSIT_VERIFY_FAILED)"},"403":{"description":"expectedRecipient is not a registered wallet (INVALID_RECIPIENT)"}}}},"/deposits/{address}":{"get":{"summary":"List deposits for a wallet address","operationId":"getDeposits","tags":["Deposits"],"security":[{"bearerJwt":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"$ref":"#/components/schemas/EthAddress"}}],"responses":{"200":{"description":"Array of deposit records"}}}},"/yield/supply":{"post":{"summary":"Supply USDC to Aave V3 to earn yield","operationId":"yieldSupply","tags":["Yield"],"security":[{"bearerJwt":[]}],"x-rate-limit":"20 req/min per account","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["depositId","amountUsdc","onBehalfOf"],"properties":{"depositId":{"type":"string","format":"uuid"},"amountUsdc":{"type":"string","pattern":"^[0-9]+$","description":"Amount in 6-decimal atomic units"},"onBehalfOf":{"$ref":"#/components/schemas/EthAddress","description":"Must be a registered wallet"}}}}}},"responses":{"200":{"description":"Supply transaction hash"},"403":{"description":"onBehalfOf not a registered wallet (INVALID_ADDRESS)"}}}},"/yield/withdraw":{"post":{"summary":"Withdraw USDC from Aave V3","operationId":"yieldWithdraw","tags":["Yield"],"security":[{"bearerJwt":[]}],"x-rate-limit":"20 req/min per account","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["depositId","amountUsdc","to"],"properties":{"depositId":{"type":"string","format":"uuid"},"amountUsdc":{"type":"string","pattern":"^[0-9]+$"},"to":{"$ref":"#/components/schemas/EthAddress","description":"Must be a registered wallet"}}}}}},"responses":{"200":{"description":"Withdrawal transaction hash"},"403":{"description":"to not a registered wallet (INVALID_ADDRESS)"}}}},"/yield/{address}":{"get":{"summary":"Get Aave aUSDC balance for a registered wallet","operationId":"getYield","tags":["Yield"],"security":[{"bearerJwt":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"$ref":"#/components/schemas/EthAddress"}}],"responses":{"200":{"description":"Aave balance record"},"403":{"description":"Address not registered to this account"}}}},"/webhooks/booking":{"post":{"summary":"Receive appointment events from genkō scheduling","operationId":"webhookBooking","tags":["Webhooks"],"x-rate-limit":"100 req/min per IP","description":"appointment.started → starts a payment stream. appointment.ended → stops it. Idempotent on x-webhook-delivery-id. Signature verified via HMAC-SHA256 (X-Webhook-Signature: sha256=<hex>).","parameters":[{"name":"x-webhook-signature","in":"header","required":true,"schema":{"type":"string"},"description":"sha256=<hmac-hex>"},{"name":"x-webhook-delivery-id","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","appointmentId","practitionerAddress","receiverAddress","ratePerMin","organizationId"],"properties":{"event":{"type":"string","enum":["appointment.started","appointment.ended"]},"appointmentId":{"type":"string","format":"uuid"},"practitionerAddress":{"$ref":"#/components/schemas/EthAddress"},"receiverAddress":{"$ref":"#/components/schemas/EthAddress"},"ratePerMin":{"type":"number","minimum":0},"organizationId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Stream started or stopped, or deduplicated"},"401":{"description":"Invalid HMAC signature (INVALID_SIGNATURE)"},"422":{"description":"Stream operation failed (STREAM_OPERATION_FAILED)"}}}}}}