Skip to main content

Rate limits

The Prop AI Deals API enforces three types of limits on every paid_* request:
  1. Per-second rate limit — Sliding-window counter, rejects with 429 when exceeded
  2. Monthly request quotaHard cap at the plan’s monthly_request_limit. No soft/hard split, no 2× tolerance. Once you hit the cap, requests are blocked until the next billing period.
  3. AI chat quota — Separate quota for AI endpoints (which also cost 5× normal requests against your monthly quota)
All limits are per account (not per IP, not per key). Your subscription buys one shared pool: every API key you create draws from the same per-second rate limit, monthly request quota, and AI chat quota. Creating more keys does not increase your limits.

Plan comparison

PlanPrice (GBP/mo)Requests / month£/requestPer-secondAI chats / monthBatch limit
Starter£9920,000£0.0049510 req/s100100
Professional£299100,000£0.0029925 req/s1,000200
Business£999400,000£0.0025050 req/s4,000500
EnterpriseCustomCustomCustomCustomCustom
All tiers are paid — there is no free tier. Every plan grants read access to properties, market data, investment calculators, spatial search, planning data, lead generators, EPC certificates, UPRN, demographics, flood risk, and heritage data, plus the ai:chat scope (the AI chat quota scales by tier — see the table above). Professional and Business additionally include build-cost:read.
Per-request value: at every tier we’re cheaper per request than the closest UK competitor AND give 20–25× higher per-second rate limits than equivalent tiers at PropertyData.
Enterprise plans are custom-provisioned (negotiated limits, dedicated infrastructure, SLAs). Email info@propaideals.co.uk to discuss. Live plan details: GET https://api.propaideals.co.uk/api/v1/api-billing/plans

How limits are enforced

Per-second rate limit

Sliding-window counter per account in Redis (shared across all your keys). If you exceed it, the request fails immediately with 429 rate_limit_exceeded:
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 1.0 seconds.",
    "retry_after": 1.0
  }
}
Sleep for retry_after seconds and retry. See retry policy.

Monthly request quota — HARD cap

The plan’s monthly_request_limit is a hard ceiling (not a soft target). Example for Professional at 100,000 requests/month:
  • Request #99,999 → ✅ 200 OK, X-Monthly-Usage: 99999
  • Request #100,000 → ✅ 200 OK, X-Monthly-Usage: 100000
  • Request #100,001 → ❌ 429 monthly_quota_exceeded
Once you’ve consumed the full quota, every subsequent request returns 429 until the calendar month rolls over or you upgrade your plan.
{
  "error": {
    "code": "monthly_quota_exceeded",
    "message": "Monthly request quota exceeded (100000/100000). Upgrade your plan or wait until the next billing period.",
    "used": 100000,
    "limit": 100000
  }
}
We recommend setting up alerts at 80% of your monthly quota so you have time to upgrade before hitting the cap. Monitor meta.usage.monthly_used on every response, or poll GET /api/v1/api-billing/usage periodically.

AI chat quota

AI endpoints (/api/v1/ultimate-ai/*) consume two budgets simultaneously:
  1. 5 requests from the monthly request quota (AI requests cost 5×)
  2. 1 AI chat call from the separate AI chat quota
When the AI quota is exhausted: 429 ai_chat_quota_exceeded. Non-AI endpoints continue to work normally until the main monthly quota is also exhausted.
{
  "error": {
    "code": "ai_chat_quota_exceeded",
    "message": "AI chat quota exceeded (1000/1000). Upgrade your plan or wait until the next billing period.",
    "used": 1000,
    "limit": 1000
  }
}
Every paid plan includes AI chat — Starter 100 chats/month, Professional 1,000, Business 4,000. When the AI quota is exhausted the AI endpoints return 429 ai_chat_quota_exceeded; all other endpoints keep working until the main monthly request quota is also used up.

Service-level backpressure (503)

During high concurrent traffic, the API server may return 503 Service temporarily busy when its database connection pool is >85% utilized. This is a protective fast-fail to prevent cascade failures — not an authentication or quota error. The response includes a Retry-After: 2 header.
{
  "error": "Service temporarily busy",
  "detail": "Database connection pool at capacity. Please retry.",
  "retry_after": 2
}
Recommended handling: retry after retry_after seconds with exponential backoff. 503s don’t count against your monthly quota — we only increment usage on successful handler execution.

Rate-limit headers

Every successful response includes these headers so you can budget without polling separately:
X-RateLimit-Limit: 25           # Per-second cap for your account (matches your plan)
X-RateLimit-Remaining: 23       # Per-second budget left in this 1-second window (account-wide)
X-Request-Cost: 1               # How much this request consumed (1 for most, 5 for AI)
X-Monthly-Usage: 4321           # Requests used this billing period (after this call)
X-Monthly-Limit: 100000         # Your plan's monthly hard cap
The same data appears in the response envelope:
{
  "meta": {
    "usage": {
      "monthly_used": 4321,
      "monthly_limit": 100000,
      "request_cost": 1
    }
  }
}

Live usage endpoint

Get the current state of all quotas in one call (uses Clerk session auth, not API key):
curl https://api.propaideals.co.uk/api/v1/api-billing/usage \
  -H "Authorization: Bearer eyJ..."
{
  "requests_used": 4321,
  "requests_limit": 100000,
  "ai_chat_used": 87,
  "ai_chat_limit": 1000,
  "plan_tier": "professional",
  "plan_name": "Professional",
  "billing_period_start": "2026-04-01T00:00:00+00:00",
  "billing_period_end": "2026-05-01T00:00:00+00:00"
}

Best practices

Throttle client-side

Most production traffic is bursty. Add a token-bucket or leaky-bucket throttle so you never hit the per-second limit in the first place.
import { RateLimiter } from 'limiter'

// Professional plan: 25 req/s — leave 10-20% headroom
const limiter = new RateLimiter({ tokensPerInterval: 22, interval: 'second' })

async function fetchProperty(id) {
  await limiter.removeTokens(1)
  return fetch(`https://api.propaideals.co.uk/api/v1/properties/${id}`, {
    headers: { Authorization: `Bearer ${process.env.PROPAIDEALS_API_KEY}` },
  }).then(r => r.json())
}
from ratelimit import limits, sleep_and_retry

# Professional plan: 25 req/s — leave 10-20% headroom
@sleep_and_retry
@limits(calls=22, period=1)
def fetch_property(property_id):
    return requests.get(
        f"https://api.propaideals.co.uk/api/v1/properties/{property_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
    ).json()
Leave 10–20% headroom below your nominal limit to absorb clock skew and transient bursts.

Cache aggressively

Properties don’t change second-to-second. A simple in-memory cache with a 5-minute TTL can cut your bill by 80%+ for read-heavy workloads.
from cachetools import TTLCache, cached

cache = TTLCache(maxsize=10_000, ttl=300)  # 5 minutes

@cached(cache)
def get_property(property_id: str) -> dict:
    return fetch_property(property_id)

Batch where possible

Use the search endpoints (GET /api/v1/properties?area=...&limit=100) instead of N parallel GET /api/v1/properties/{id} calls. One paginated request returns up to 100 properties for the cost of one request — saves 99 quota units and improves latency.

Monitor meta.usage

Log meta.usage.monthly_used on every response. Page yourself when usage crosses 80% of your monthly limit so you can upgrade before hitting the hard cap. For Professional (100,000 requests/month), 80% = 80,000 requests. At 25 req/s sustained, that’s ~53 minutes. At 5 req/s sustained, that’s ~4.4 hours. Set your alerting accordingly.

Handle 429 and 503 with exponential backoff

Both 429 rate_limit_exceeded (per-second) and 503 Service temporarily busy (server pressure) are transient and should be retried with backoff. 429 monthly_quota_exceeded is NOT transient — don’t retry, upgrade instead. See Errors › Recommended retry policy for a copy-paste retry helper.

Use separate keys per environment

Give production, staging, and CI each their own key so you can revoke or rotate one without disrupting the others, and tell traffic apart in GET /api/v1/api-billing/logs and /usage/daily. Note: all keys on your account share one quota pool — separate keys improve hygiene and attribution, they do not raise your limits, so a runaway CI script still draws down the same monthly quota. Add a client-side throttle to non-production keys if that’s a concern. You can have up to 5 active keys per account.