Rate Limits & Credits
Understand how API rate limiting works and how credits are calculated for optimization solves.
Rate Limits
API requests are rate-limited per organization to ensure fair usage across all tenants.
Per-Endpoint Limits
| Endpoint | Limit (per minute) | Limit (per day) |
|---|---|---|
POST /api/v2/solve | 60 | 10,000 |
POST /api/v2/models/*/execute | 60 | 10,000 |
GET /api/v2/models/* | 120 | 50,000 |
POST /api/v2/credits/calculator | 120 | 50,000 |
POST /api/v2/auth/login | 10 | 1,000 |
POST /api/v2/auth/password-reset | 3/hour | -- |
| All other endpoints | 120 | 50,000 |
Rate Limit Headers
Every API response includes rate limit headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1709827200
Content-Type: application/json| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Number of requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the current window resets |
When Rate Limited
When you exceed the limit, the API returns a 429 Too Many Requests response:
{
"error": "rate_limit_exceeded",
"message": "You have exceeded your rate limit of 60 requests/minute",
"limit": 60,
"remaining": 0,
"reset_at": 1709827200,
"retry_after": 23
}Handling Rate Limits
Check rate limit headers proactively and implement exponential backoff for retries.
Python
import time
import httpx
API_KEY = "ok_live_..."
BASE_URL = "https://api.jaot.io"
def rate_aware_solve(payload, max_retries=3):
"""Solve with rate limit awareness."""
for attempt in range(max_retries + 1):
response = httpx.post(
f"{BASE_URL}/api/v2/solve",
headers={"Authorization": f"Bearer {API_KEY}"},
json=payload,
)
if response.status_code == 429:
if attempt == max_retries:
response.raise_for_status()
retry_after = response.json().get("retry_after", 2 ** attempt)
print(f"Rate limited. Retrying in {retry_after}s...")
time.sleep(retry_after)
continue
response.raise_for_status()
return response.json()
result = rate_aware_solve({"variables": [...], "constraints": [...]})JavaScript
async function rateLimitedFetch(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
if (attempt === maxRetries) throw new Error("Rate limit exceeded");
const retryAfter = parseInt(response.headers.get("retry-after") || "1");
const wait = retryAfter || Math.pow(2, attempt);
console.log(`Rate limited. Retrying in ${wait}s...`);
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
return response;
}
}Tip: Implement exponential backoff in your retry logic to avoid overwhelming the API during rate limit windows.
Credit System Overview
Credits are the unit of compute in JAOT. Each optimization solve consumes credits based on the problem's complexity. More variables, constraints, and solver time require more credits.
Credit Pricing Formula
The credit cost for each solve is calculated using this formula:
credits = max(1, round(base + variable_cost + integer_cost + constraint_cost + time_cost))
Where each component is:
| Component | Formula | Description |
|---|---|---|
base | 1 | Fixed cost per solve |
variable_cost | num_variables * 0.1 | Cost per decision variable |
integer_cost | num_integer_or_binary_vars * 0.5 | Extra cost for integer and binary variables (harder to solve) |
constraint_cost | num_constraints * 0.1 | Cost per constraint |
time_cost | 1 if time_limit > 60s, else 0 | Flat surcharge when time limit exceeds 60 seconds |
The minimum credit cost is always 1, regardless of the calculated value.
Worked Example
Consider a furniture production problem with:
- 10 decision variables (5 integer, 0 binary)
- 8 constraints
- 120-second time limit
Step-by-step calculation:
| Component | Calculation | Value |
|---|---|---|
| Base | Fixed | 1.00 |
| Variable cost | 10 * 0.1 | 1.00 |
| Integer cost | 5 * 0.5 | 2.50 |
| Constraint cost | 8 * 0.1 | 0.80 |
| Time cost | 120 > 60, so 1 | 1.00 |
| Total | max(1, round(1 + 1 + 2.5 + 0.8 + 1)) | 6 credits |
The total raw value is 6.30, which rounds to 6 credits.
Cost in EUR by plan
| Plan | Cost per Credit | Total Cost |
|---|---|---|
| Free | 0.000 | Free (limited quota) |
| Starter (€19 / 600 cr) | 0.032 | 0.192 |
| Pro (€49 / 2,500 cr) | 0.020 | 0.120 |
| Business (€149 / 10,000 cr) | 0.015 | 0.090 |
Credit Calculator API
Use the public credit calculator endpoint to estimate costs before submitting problems. No authentication required.
Request
curl -X POST https://api.jaot.io/api/v2/credits/calculator \
-H "Content-Type: application/json" \
-d '{
"num_variables": 10,
"num_integer_vars": 5,
"num_binary_vars": 0,
"num_constraints": 8,
"time_limit_seconds": 120
}'Response
{
"credits_required": 6,
"breakdown": {
"base": 1,
"variable_cost": 1.0,
"integer_cost": 2.5,
"constraint_cost": 0.8,
"time_cost": 1.0
},
"cost_eur": 0.120,
"cost_by_plan": {
"free": 0,
"starter": 0.192,
"pro": 0.120,
"business": 0.090
}
}Python
import httpx
response = httpx.post(
"https://api.jaot.io/api/v2/credits/calculator",
json={
"num_variables": 10,
"num_integer_vars": 5,
"num_binary_vars": 0,
"num_constraints": 8,
"time_limit_seconds": 120,
},
)
estimate = response.json()
print(f"Credits needed: {estimate['credits_required']}")
print(f"Breakdown: {estimate['breakdown']}")
print(f"Cost (Pro plan): EUR {estimate['cost_by_plan']['pro']}")Tip: Use the credit calculator to estimate costs before submitting problems, especially for batch operations or sensitivity analyses.
Checking Balance
Check your current credit balance via the API:
curl -H "Authorization: Bearer ok_live_..." \
https://api.jaot.io/api/v2/credits/balancePurchasing Credits
Credits can be purchased through the billing portal or via the checkout API endpoints. See the Credits and Billing API documentation for available packages and pricing.
Available top-up packages:
| Package | Credits | Price (EUR) | Per Credit |
|---|---|---|---|
| Starter | 500 | 14.00 | 0.028 |
| Growth | 2,000 | 48.00 | 0.024 |
| Pro | 5,000 | 100.00 | 0.020 |
| Business | 20,000 | 320.00 | 0.016 |