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.

Surqo uses a freemium model designed to let any farmer start for free and upgrade when they need more AI power. The Free plan provides enough capacity to evaluate the platform and monitor one farm indefinitely — real-time sensor data, KPIs, and email alerts are always unlimited. The AI analysis and chat features are governed by a quota tracked in the user_profiles table so every request is consistent across devices and sessions.

Plans Comparison

FeatureFreePro
Price$0$15–25 USD/month
Farms1Unlimited
AI analyses (lifetime)4Unlimited
Max tokens per analysis8002,048
Token budget (lifetime)3,200Unlimited
Email alertsUnlimitedUnlimited
Chat with AI agronomistYes (token budget)Unlimited
Priority supportNoYes

How Quota Enforcement Works

Surqo tracks two counters on every user profile:
  • analyses_used — incremented by 1 each time POST /api/v1/analysis/analyze completes successfully.
  • tokens_used — incremented by the number of output tokens generated in each analysis or chat turn.
Before processing any AI request the backend calls current_user.can_use_ai_analysis (for analyses) or current_user.can_use_chat (for chat). Both properties read directly from the database row — there is no separate cache layer — so quota changes take effect immediately. When a quota is exceeded the API returns HTTP 402 with a structured error body:
{
  "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"
}

Handling 402 in Code

try {
  const analysis = await analysisAPI.analyze({
    farm_name: farm.name,
    lat: farm.latitude,
    lon: farm.longitude,
    crop_type: farm.crop_type,
    farm_id: farm.id
  })
} catch (error: any) {
  if (error.status === 402) {
    const detail = error.detail
    // detail.code === 'analysis_quota_exceeded'
    // detail.analyses_used, detail.analyses_limit
    // detail.contact_url
    console.log(`Quota exceeded: ${detail.message}`)
    // Redirect to upgrade page
    router.push('/upgrade')
  }
}

Check Quota Before Calling Analyze

Poll GET /api/v1/users/me/plan-limits before displaying the analysis button to give users a clear view of their remaining capacity:
// GET /api/v1/users/me/plan-limits
const limits = await fetch('/api/v1/users/me/plan-limits', {
  headers: { Authorization: `Bearer ${token}` }
}).then(r => r.json())

if (!limits.ai_analysis.allowed) {
  console.log(`Analyses used: ${limits.ai_analysis.used}/${limits.ai_analysis.limit}`)
  // Show upgrade prompt instead of the analyze button
} else {
  console.log(`Analyses remaining: ${limits.ai_analysis.remaining}`)
}

Upgrading a User to Pro

Plan upgrades are currently performed manually via the admin endpoint. Stripe integration is planned but not yet implemented.
curl -X PATCH https://surqo-api.fly.dev/api/v1/users/<user-id>/plan \
  -H "Authorization: Bearer <admin-jwt>" \
  -H "Content-Type: application/json" \
  -d '{"plan": "paid"}'
On upgrade to "paid" the backend sets plan_activated_at to the current UTC timestamp and all quota checks immediately return unlimited. Downgrading back to "free" restores the quota limits against the same analyses_used and tokens_used counters — no counter reset occurs on downgrade.
Admin JWTs must belong to a user with is_admin = true in user_profiles. The endpoint returns 403 for all other callers, including regular Pro users.

Free Tier Quota Summary

The Free tier includes:
  • 1 farm maximum
  • 4 AI analyses total (lifetime, not monthly)
  • 800 tokens maximum per analysis response
  • 3,200 total token budget for chat across all sessions
  • Unlimited email alerts — no monthly cap
Real-time sensor monitoring, KPIs, WebSocket live feed, and device pairing are fully available on the Free plan with no usage limits.

Quota Properties in Code

The constants are defined on the UserProfile model and used consistently across all quota checks:
# UserProfile quota constants
FREE_ANALYSES_LIMIT             = 4      # AI analyses lifetime
FREE_TOKENS_LIMIT               = 3_200  # output tokens lifetime
FREE_OUTPUT_TOKENS_PER_ANALYSIS = 800    # max tokens per analysis
PAID_OUTPUT_TOKENS_PER_ANALYSIS = 2_048  # max tokens per analysis (Pro)
MAX_FARMS                       = 1      # farms allowed on free plan

@property
def can_use_ai_analysis(self) -> bool:
    if self.is_admin or self.is_paid:
        return True
    return self.analyses_used < self.FREE_ANALYSES_LIMIT

@property
def can_use_chat(self) -> bool:
    if self.is_admin or self.is_paid:
        return True
    return self.tokens_used < self.FREE_TOKENS_LIMIT

@property
def max_output_tokens(self) -> int:
    return (
        self.PAID_OUTPUT_TOKENS_PER_ANALYSIS
        if self.is_paid
        else self.FREE_OUTPUT_TOKENS_PER_ANALYSIS
    )

Build docs developers (and LLMs) love