Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ricardomb-tech/surqo/llms.txt

Use this file to discover all available pages before exploring further.

Every protected Surqo endpoint expects a Supabase-issued JWT signed with the ES256 algorithm (P-256 curve). Pass the token in the standard Authorization: Bearer <token> header. The backend validates the JWT against Supabase’s public JWKS endpoint, extracts the sub claim as the user’s UUID, and — on the very first valid request — automatically creates a UserProfile with plan = "free" and analyses_used = 0. There is no separate registration step.

Auth Flow

Client                Supabase Auth           FastAPI Backend
  │                        │                       │
  │─ signInWithPassword() ─►│                       │
  │◄─ JWT (ES256) ──────────│                       │
  │                         │                       │
  │─ GET /api/v1/farms/ ─────────────────────────►  │
  │  Authorization: Bearer <jwt>                    │
  │                         │  validate JWKS        │
  │                         │  → sub = user UUID    │
  │                         │  → get/create         │
  │                         │    UserProfile        │
  │◄─ [user farms] ─────────────────────────────── │

Obtaining a JWT Token

The Surqo frontend uses @supabase/ssr. Call signInWithPassword and read data.session.access_token.
import { createBrowserClient } from '@supabase/ssr'

const supabase = createBrowserClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'password'
})

const token = data.session?.access_token
For subsequent requests the frontend uses getAccessToken() from lib/auth.ts, which calls supabase.auth.getSession() and returns the access token from the active session — automatically refreshing it when close to expiry.

Using the Token

Pass the JWT in the Authorization header on every authenticated request.
curl https://surqo-api.fly.dev/api/v1/farms/ \
  -H "Authorization: Bearer eyJhbGciOiJFUzI1NiIs..."
All endpoints that require authentication return 401 Unauthorized if the header is missing, malformed, or the token has expired.

JWT Validation Details

The following describes how the FastAPI backend validates incoming tokens — relevant for backend integrators, custom tooling, and debugging auth issues.
1

JWKS lookup (primary path)

The backend fetches the Supabase public key set from:
https://<project>.supabase.co/auth/v1/.well-known/jwks.json
The response is cached in-process for 1 hour (TTL = 3 600 s), re-fetched on the next request after expiry. This means the public key is loaded at most once per hour — not on every request.
2

HS256 secret fallback (legacy)

If SUPABASE_JWT_SECRET is set in the environment, the backend attempts HS256 verification as a secondary fallback. This supports older Supabase project configurations that issued HS256 tokens.
3

Manual EC coordinate fallback

If SUPABASE_JWK_X and SUPABASE_JWK_Y environment variables are set, the backend constructs the P-256 public key directly from the x and y coordinates without a network call. This is the recommended fallback for air-gapped or network-restricted deployments.
SUPABASE_JWK_X=<base64url-x-coordinate>
SUPABASE_JWK_Y=<base64url-y-coordinate>
SUPABASE_JWK_KID=<key-id>
PropertyValue
AlgorithmES256 (P-256 curve)
Expected audienceauthenticated
User identifier claimsub (UUID, matches user_profiles.user_id)
Key sourceSupabase JWKS endpoint /auth/v1/.well-known/jwks.json
Key cache duration1 hour (in-process)
Rate limiting also uses the JWT sub claim when a token is present, ensuring that an authenticated user cannot evade per-user limits by rotating IP addresses. The signature is not re-verified for rate-limit keying — only for actual authorization.

Auto-Profile Creation

The first authenticated request to any protected endpoint triggers automatic profile provisioning. No explicit registration call is required.
First valid JWT request
  → backend reads sub claim → UUID
  → SELECT * FROM user_profiles WHERE user_id = <uuid>
  → not found → INSERT user_profiles (plan='free', analyses_used=0)
  → returns new UserProfile
Subsequent requests simply look up the existing profile. The creation is transparent — your client code does not need to handle it.
The freshly created profile starts with:
FieldDefault
planfree
analyses_used0
tokens_used0
email_alerts_this_month0

WebSocket Authentication

WebSocket connections cannot carry custom headers in most browser environments. Pass the JWT as a query parameter named token instead.
// JWT passed as query parameter (not header)
const ws = new WebSocket(
  `wss://surqo-api.fly.dev/api/v1/sensors/ws/live/${farmId}?token=${jwtToken}`
)

ws.onmessage = (event) => {
  const reading = JSON.parse(event.data)
  console.log('Live reading:', reading)
}
WebSocket connections are closed with custom close codes on auth failure — not HTTP 401. Handle these in your onclose handler:
Close codeReason
4001Missing or invalid token
4002Invalid farm UUID format
4003Farm does not belong to the authenticated user

Plan-Gated Endpoints

Certain endpoints enforce free-tier quotas. When a quota is exhausted the API responds with 402 Payment Required and a structured error body — never a generic error string.
{
  "code": "analysis_quota_exceeded",
  "message": "Usaste tus 4 análisis IA gratuitos. Contacta a nuestro equipo para activar el plan premium.",
  "analyses_used": 4,
  "analyses_limit": 4,
  "contact_url": "https://surqo.co/upgrade"
}
Check GET /api/v1/users/me/plan-limits at any time to inspect remaining allowances before making quota-consuming requests:
curl https://surqo-api.fly.dev/api/v1/users/me/plan-limits \
  -H "Authorization: Bearer <jwt>"
{
  "plan": "free",
  "farms": { "used": 1, "limit": 1, "remaining": 0, "unlimited": false },
  "ai_analysis": {
    "allowed": true,
    "used": 2,
    "limit": 4,
    "remaining": 2,
    "tokens_used": 1600,
    "tokens_limit": 3200,
    "max_tokens_per_analysis": 800
  },
  "email_alerts": { "unlimited": true, "used_this_month": 3 }
}

Build docs developers (and LLMs) love