Skip to main content

๐Ÿค– Agent API + MCP Server

Pay-per-minute REST and MCP endpoints that let AI agents submit tennis videos to the AceSense GPU pipeline and retrieve coaching reports.

:::tip TL;DR

  • REST base: https://api.acesense.io/v1
  • MCP base: https://api.acesense.io/mcp
  • Auth: Authorization: Bearer phk_<keyId>_<secret> (SHA-256-hashed at rest, constant-time compare)
  • Pricing: โ‚ฌ0.75/min of submitted video (โ‰ˆ $0.80 / ยฃ0.65), prepaid credits, โ‚ฌ10 free credit per new key
  • Quota: per-key sliding-window (default 10 req/min, 200 jobs/day)
  • Discovery: /.well-known/api-catalog, /.well-known/mcp/server-card.json, /.well-known/x402, /.well-known/mpp.json, /.well-known/acp.json :::

๐ŸŽฏ What this is forโ€‹

The AceSense Flutter app is one consumer of the GPU pipeline. The agent API is another. Same five-step pipeline (TrackNet โ†’ court keypoints โ†’ MediaPipe pose โ†’ CatBoost classification โ†’ stroke quality), exposed via two protocols an AI agent can speak fluently:

  • REST for general HTTP clients, OpenAPI 3.1 described.
  • MCP for native LLM-tool integration (Claude desktop, ChatGPT custom GPTs, agent SDKs).

Customers are tennis-coaching bots, training-platform integrations, club-management software, dataset-enrichment pipelines.


๐Ÿ—๏ธ Where it livesโ€‹

SurfaceRepo pathFunction name
REST APIacesense-auth-function/api/apiServer (onRequest, region europe-west1)
MCP serveracesense-auth-function/mcp/mcpServer (onRequest, region europe-west1)
API key callablesacesense-auth-function/api/keys.tscreateApiKey, listApiKeys, revokeApiKey
Rate limiteracesense-auth-function/api/rate-limit.ts(internal)
Billing meteracesense-auth-function/api/billing-meter.ts(internal)
Marketing landingacesense-landing/src/content/static/agents.md/agents route
Discovery filesacesense-landing/scripts/prerender.mjs/.well-known/*

The custom domain api.acesense.io is configured via Firebase Hosting rewrites (set up separately from the function deploy).


๐Ÿ”‘ API key architectureโ€‹

Stored at api_keys/{keyId} in Firestore:

interface ApiKey {
keyId: string // 'phk_<base64url-rand-16>'
userId: string // owning Firebase user
hashedSecret: string // sha256(fullKey)
name: string
plan: 'free' | 'pro' | 'team'
createdAt: Timestamp
lastUsedAt: Timestamp | null
revokedAt: Timestamp | null
balanceCents: number // prepaid credit balance
spentCentsTotal: number
rateLimit: { perMinute: number; perDay: number }
allowedOrigins?: string[]
}
  • Wire format: phk_<keyId>_<secret>. The secret is only shown to the user once at creation.
  • All comparisons via crypto.timingSafeEqual to avoid timing attacks.
  • lastUsedAt is best-effort updated on every successful call.
  • Soft-revoke sets revokedAt; the doc stays for audit.

๐Ÿ’ธ Billing flowโ€‹

  • estimateJobCostCents = ceil(durationSeconds / 60) * pricePerMinuteCents โ€” default 75ยข (โ‚ฌ0.75/min), overridable via API_PRICE_PER_MINUTE_CENTS.
  • quoteAndReserve is an atomic Firestore transaction at POST /v1/jobs โ€” if balance < cost the job is rejected before any RunPod work.
  • assertJobBudget runs again inside processVideoOnUpload as defence-in-depth (a job that reserved budget but then balance got refunded mid-flight is rejected).
  • creditFailedJob refunds the reserved cost on pipeline failure.
  • Balance top-ups are manual for now via adminAdjustApiKeyBalance; no checkout/webhook endpoint is deployed.

๐Ÿšง Rate limitingโ€‹

Per-key sliding window backed by Firestore at api_keys/{keyId}/rate_buckets/{minuteKey}:

  • Default: 10 req/min + 200 jobs/day
  • Plan-tier keys (set via the user's billing page) can raise these
  • Returns HTTP 429 + Retry-After: <seconds> when exceeded
  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers on every response

For MCP, the same limiter applies. Exceeded โ†’ JSON-RPC error code -32002 "rate limited".


๐Ÿ”ง Tools exposed by the MCP serverโ€‹

ToolPaid?Description
analyze_tennis_videoโœ…Submit a video URL; gates on balanceCents >= cost; refunds on ingest failure
get_job_statusโ€“Poll { status, progress, error? }
get_analysis_resultโ€“Fetch the completed analysis JSON
get_pricingโ€“Current per-minute pricing across EUR/USD/GBP
get_my_usageโ€“Balance + spend + jobs today/this month
list_my_jobsโ€“Paginated list of recent jobs for the calling key

Resources (acesense://*.md) point at the marketing markdown twins (pricing, accuracy, how-it-works, faq, compare/{competitor}) and are fetched on-demand.


๐Ÿ›ก๏ธ Defence-in-depthโ€‹

The same threat-model controls protect both the in-app upload path and the agent API:

  • Per-user quota (shared/quota.ts) โ€” atomic Firestore transactions; concurrent slot rolled back on Firestore-write failure.
  • Magic-byte gate (shared/video-validation.ts) โ€” first 64 bytes checked for ISO-BMFF / AVI / MKV signatures before RunPod dispatch. Failed jobs marked 'invalid_file'.
  • 30-min signed-URL TTL (shared/signed-url.ts) โ€” v4 signed URLs with explicit expires; SHA-256-hashed audit row in signed_url_audit/{id}.
  • ffprobe gate โ€” deferred. Hook point + tolerance helper are wired (withinTolerance in shared/video-validation.ts); needs @ffprobe-installer/ffprobe (~30 MB) bundled in the deploy.
  • Locked Firestore rules โ€” jobs/{id} updates whitelist userTitle, userTags, userNotes, isFavorite, updatedAt; everything else is server-only.

See Malicious-upload threat model for the full P0/P1 list and remaining open items.


๐ŸŒ Discovery surfaceโ€‹

These files are emitted by the landing-site prerender step and served as static JSON:

PathPurpose
/.well-known/api-catalogRFC 9727 linkset of public retrievable resources
/.well-known/mcp/server-card.jsonSEP-1649 MCP card (live, version 0.2.0)
/.well-known/agent-skills/index.jsonCloudflare Agent Skills RFC v0.2.0 catalogue
/.well-known/openid-configurationOIDC discovery (Firebase Auth issuer)
/.well-known/oauth-authorization-serverRFC 8414
/.well-known/oauth-protected-resourceRFC 9728
/.well-known/x402x402 payment-required discovery
/.well-known/mpp.jsonMachine Payment Protocol
/.well-known/acp.jsonAgentic Commerce Protocol (transactional: true)
/.well-known/ucpUniversal Commerce Protocol
/openapi.jsonOpenAPI 3.1

๐Ÿš€ Setup checklist (after deploy)โ€‹

  1. Deploy Cloud Functions: cd acesense-auth-function && pnpm deploy (or firebase deploy --only functions).
  2. Configure Firebase Hosting custom domain api.acesense.io with rewrites:
    • /v1/** โ†’ function apiServer
    • /mcp โ†’ function mcpServer
  3. Test the public API key flow: sign in as a user, call createApiKey({ name: 'test', plan: 'free' }), capture the returned fullKey.
  4. Credit test balance: use adminAdjustApiKeyBalance for any paid-path smoke test.
  5. Smoke-test the REST API: curl -H "Authorization: Bearer phk_..." https://api.acesense.io/v1/healthz.
  6. Smoke-test the MCP server: POST /mcp with {"jsonrpc":"2.0","id":1,"method":"tools/list"}. Confirm the 6 tools are returned.