API Reference
The MakerWorld Estimator API provides endpoints for fetching filament presets, scraping model metadata from MakerWorld, uploading geometry files for instant cost estimates, and querying the community directory and leaderboard.
Authentication
Most endpoints are open. If the server administrator has set the API_KEY environment variable, all requests must include one of these headers:
Authorization: Bearer YOUR_API_KEY
# — or —
X-API-Key: YOUR_API_KEY
/api/estimate/upload endpoint is rate-limited to 5 requests per minute per IP. All other endpoints are unrestricted.Authorization: Bearer <token> plus the matching userId / X-User-Id. Accounts created with email & password must verify their address first — until then calculation endpoints return 403 with code: "email_not_verified" (sign-in and account pages stay available). See /api/auth/send-verification and /api/auth/verify-email.Endpoint Index
| Method | Path | Description |
|---|---|---|
| GET | / |
Service health check & link directory |
| GET | /api/presets |
Filament colours, multipliers, hardware kits |
| GET | /api/printers |
Printer reference list (build plate, wattage) |
| GET | /api/image |
CORS-safe model image relay for share cards |
| GET | /api/detect-currency |
Geo-IP currency resolution |
| GET | /api/models |
Searchable model directory index |
| GET | /api/stats |
Public catalogue counters |
| GET | /api/leaderboard |
All-time popular & weekly trending models |
| GET | /api/scrape |
MakerWorld model scrape & DB cache (signed-in user, 1 credit) |
| GET | /api/scrape/collection |
Scrape all models in a MakerWorld collection (signed-in user, 1 credit per model) |
| POST | /api/estimate/upload |
Upload STL / 3MF and get cost estimate |
| POST | /api/auth/send-verification |
Send (or resend) the account verification email |
| POST | /api/auth/verify-email |
Redeem an email verification token |
| POST | /api/user/email-preferences |
Opt in/out of product-update emails |
All responses use Content-Type: application/json. The upload endpoint accepts multipart/form-data.
Service Status
Returns a 200 OK when the service is up, along with a directory of all endpoint paths and links.
Request
curl https://api.tryar.in/
Response 200 OK
{
"status": "OK",
"name": "MakerWorld Estimator API",
"version": "1.0.0",
"endpoints": {
"presets": "/api/presets",
"detectCurrency": "/api/detect-currency",
"models": "/api/models",
"leaderboard": "/api/leaderboard",
"scrape": "/api/scrape?url={model_url}",
"upload": "/api/estimate/upload"
},
"links": {
"homepage": "https://estimator.tryar.in",
"docs": "https://estimator.tryar.in/api"
}
}
Get Presets
Returns the shared filament colour lookup table (name, hex, cost multiplier) and hardware kit accessory presets consumed by the frontend and browser extensions.
Request
curl https://api.tryar.in/api/presets
Response 200 OK
{
"presetColors": [
{
"hex": "#000000",
"name": "Basic Black",
"multiplier": 1
},
{
"hex": "#ffd700",
"name": "Silk Gold",
"multiplier": 1.5
}
],
"hardwarePresets": [
{
"keyword": "keychain",
"name": "Keychain Ring",
"cost": 5
}
]
}
List Printers
Returns the printer reference list used by the Pricing Settings printer picker. Each row carries the build plate size and the typical average power draw while printing PLA — the value that feeds the energy cost line ((power / 1000) × print time × kWh price). maxPowerWatts is the informational PSU peak. Served from the printers database collection with a 300 s cache; a bundled fallback list is returned during a database outage (source: "bundled"). Public endpoint, no authentication.
Request
curl https://api.tryar.in/api/printers
Response 200 OK
{
"success": true,
"source": "db",
"printers": [
{
"name": "P1S",
"brand": "Bambu Lab",
"buildPlate": "256 × 256 mm",
"powerWatts": 120,
"maxPowerWatts": 1000
},
{
"name": "MK4S",
"brand": "Prusa Research",
"buildPlate": "250 × 210 mm",
"powerWatts": 80,
"maxPowerWatts": 240
}
]
}
Share Image Relay
Proxies a public model image from an allowlisted platform CDN and re-serves it from the API origin with Access-Control-Allow-Origin: *. The 1080 × 1080 share card is drawn on a canvas; without this relay the remote image would taint the canvas and canvas.toBlob() would fail. HTTPS only, strict hostname allowlist, real-image sniffing (PNG / JPEG / WebP / GIF), 8 MB cap. MakerWorld hero images are often larger than the cap, so its CDN resizes them on request: pass w (16–2048 px) for the width you need. An oversized original without w is resized to 2048 px. Other hosts ignore w. Public read-only; responses cache for one day.
Request
curl "https://api.tryar.in/api/image?url=https%3A%2F%2Fmakerworld.bblmw.com%2Fmakerworld%2Fmodel%2FUSb8d284c467a152%2Fdesign%2F2023-04-13_cff25c96d0b358.png&w=1168" --output model.png
Response 200 OK
Content-Type: image/png
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
Access-Control-Allow-Origin: *
X-Content-Type-Options: nosniff
Errors: 400 missing / invalid / non-HTTPS URL, 403 host not allowlisted, 413 image over 8 MB that the CDN cannot resize, 415 not a real image, 502 upstream fetch failed.
Detect Currency
Resolves a billing currency and suggested UI locale from the incoming request's geographic headers (set automatically by Cloudflare) plus the optional Accept-Language header. Returns the detected country code, mapped currency string, and a locale suggestion (suggestedLocale / htmlLang / urlPrefix) — a hint only; the URL prefix, a stored preference, and hreflang remain authoritative.
Request
curl https://api.tryar.in/api/detect-currency
# Cloudflare sets CF-IPCountry automatically.
# Override for local testing:
curl -H "CF-IPCountry: US" \
https://api.tryar.in/api/detect-currency
Response 200 OK
{
"success": true,
"country": "DE",
"currency": "EUR",
"suggestedLocale": "de",
"htmlLang": "de",
"urlPrefix": "de"
}
// Currency mappings:
// IN → INR | US → USD | GB → GBP | CN → CNY
// DE,FR,IT,ES,NL,AT,BE,IE,PT,… → EUR | JP → JPY
// AU → AUD | CA → CAD | SG → SGD | BR → BRL | KR → KRW | RU,BY → RUB
// Fallback for all other countries → USD
// Locale mappings (country primary; Accept-Language overrides IN/CA/CH):
// DE,AT,CH → de | FR,BE,LU,MC → fr | JP → ja
// CN,TW,HK,MO,MY → zh-Hans | KR → ko | IT → it
// ES + LatAm → es | BR,PT → pt-BR | RU,CIS → ru
// IN/CA/US/GB/AU/IE/NZ/SG → en (hi for Accept-Language: hi)
// Fallback → Accept-Language, then en
Catalogue Stats
Returns aggregate counters for the public directory. The homepage uses totalModels for its trust line. No authentication and no credits are required, and the response is cached for 10 minutes.
Response 200 OK
{
"success": true,
"totalModels": 28123,
"updatedAt": "2026-10-27T04:00:00.000Z"
}
Models Directory
Returns a paginated list of cached/indexed print profiles from the database. Supports full-text search on model titles and creator names, sort ordering, and price-range filtering.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| q | string | optional | — | Search keyword matched against model title and creator name. |
| sort | string | optional | popular |
Sort order. One of: popular · recent · weight · time · price_asc · price_desc |
| currency | string | optional | INR |
ISO currency code. Returns, filters and sorts on that currency's stored estimate. Falls back to INR when the per-currency columns are not provisioned. |
| min_price | number | optional | — | Filter models with the selected currency estimate greater than or equal to this amount. |
| max_price | number | optional | — | Filter models with the selected currency estimate less than or equal to this amount. |
| limit | integer | optional | 60 |
Page size (1–500). Larger values mean larger payloads — page instead of requesting everything. |
| page | integer | optional | 1 |
1-based page number. Combine with totalPages from the response. |
Cache-Control: public, max-age=60, stale-while-revalidate=300. Add nocache=true to bypass the server cache (no-store).Request
curl "https://api.tryar.in/api/models\
?q=keychain&sort=popular&limit=24&page=2"
Response 200 OK
{
"success": true,
"page": 2,
"perPage": 24,
"totalItems": 483,
"totalPages": 21,
"models": [
{
"profileId": 1254079,
"modelId": 1717122,
"modelName": "Lamborghini Keychain",
"modelImage": "https://...",
"profileName": "Default Profile",
"printTimeHours": 0.63,
"weightGrams": 10,
"plateCount": 1,
"estimatedCost": 150,
"creatorName": "3DSpec_pl",
"license": "Standard Digital File License",
"cleanUrl": "https://makerworld.com/en/models/1717122"
}
]
}
Leaderboard
Returns the top 10 all-time models ranked by calculation count, and the top 10 trending models calculated within the last 7 days.
Request
curl https://api.tryar.in/api/leaderboard
Response 200 OK
{
"success": true,
"topAllTime": [
{
"profileId": 1254079,
"modelName": "Lamborghini Keychain",
"estimatedCost": 150,
"calculationCount": 120
}
],
"trending": [
{
"profileId": 1254079,
"modelName": "Lamborghini Keychain",
"estimatedCost": 150,
"recentCalculations": 45
}
]
}
Scrape & Cache
Fetches print profile metadata for a supported model URL, returns structured plate and filament data, and caches MakerWorld results in the database. Supported platforms: MakerWorld (cached), Printables, Thingiverse and Cults3D — the latter two only when the server's THINGIVERSE_TOKEN / CULTS3D_API_USER+CULTS3D_API_KEY secrets are configured. Non-MakerWorld estimates are stateless (not persisted) and Cults3D returns metadata with requiresUpload: true instead of a cost.
Authentication & Credits (server-enforced)
This endpoint requires a signed-in user session. Send the PocketBase token as Authorization: Bearer <token> (the x-pb-token header also works) together with the matching userId. The server verifies the token belongs to that user (401 otherwise). One credit is checked before any upstream work (402 if the balance is below 1) and deducted after a successful calculation — failed scrapes are never charged. Accounts without a verified email address receive 403 with code: "email_not_verified" until they confirm the link sent by /api/auth/send-verification. If the debit cannot be recorded, the endpoint fails closed with 503 (code: "credit_sync_failed") and withholds the result, so an uncharged calculation is never served.
Headers
| Header | Type | Required | Default | Description |
|---|---|---|---|---|
| Authorization | string | required | — | User PocketBase session token as Bearer <token> (or use the x-pb-token header). |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| userId | string | required | — | PocketBase user ID. Must match the token owner (X-User-Id header also accepted). |
| url | string | required | — | Full supported model page URL (MakerWorld, Printables, Thingiverse, Cults3D). MakerWorld model IDs are extracted via /\/models\/(\d+)/; other platforms use their connector's URL grammar. |
| nocache | boolean | optional | false |
Set to true to bypass DB cache and force a fresh fetch. |
Request
curl "https://api.tryar.in/api/scrape\
?userId=usr_abc123\
&url=https%3A%2F%2Fmakerworld.com\
%2Fen%2Fmodels%2F1717122" -H "Authorization: Bearer <pb-token>"
# Force cache bypass:
curl "https://api.tryar.in/api/scrape\
?userId=usr_abc123&url=...&nocache=true" -H "Authorization: Bearer <pb-token>"
Response 200 OK
{
"success": true,
"modelName": "Lamborghini Keychain",
"profileId": 1254079,
"profileName": "Default Profile",
"printTimeHours": 0.63,
"weightGrams": 10,
"plateCount": 1,
"plates": [
{
"index": 1,
"prediction": 2280,
"weight": 10,
"filaments": [
{ "type": "PLA", "color": "#ffdd00", "usedG": 10 }
]
}
],
"license": "Standard Digital File License",
"cleanUrl": "https://makerworld.com/en/models/1717122",
"creditBalance": 9,
"creditsDeducted": 1
}
Scrape Collection
Fetches MakerWorld collection details and batch-scrapes/looks up all models in the collection in parallel. If any individual model fails to scrape, its error is captured and returned inside the models array so that the rest of the collection continues to compute.
Authentication & Credits (server-enforced)
Same session contract as /api/scrape — Authorization: Bearer <token> plus matching userId (401 otherwise). The call costs 1 credit per model: the balance is checked before per-model scraping starts (402 when below the model count), and only models that actually succeed are charged. A backend memory-cache hit returns the cached result for 0 credits. If the debit cannot be recorded, the call fails closed with 503 (code: "credit_sync_failed") and withholds the collection.
Headers
| Header | Type | Required | Default | Description |
|---|---|---|---|---|
| Authorization | string | required | — | User PocketBase session token as Bearer <token> (or use the x-pb-token header). |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| userId | string | required | — | PocketBase user ID. Must match the token owner (X-User-Id header also accepted). |
| url | string | required | — | Full URL of the MakerWorld collection page (e.g. https://makerworld.com/en/collections/27910720-large-prints). |
| nocache | boolean | optional | false | Set to true to force fresh MakerWorld fetches for each model. |
Request
curl "https://api.tryar.in/api/scrape/collection?userId=usr_abc123&url=https://makerworld.com/en/collections/27910720-large-prints" -H "Authorization: Bearer <pb-token>"
Response 200 OK
{
"success": true,
"isCollection": true,
"collectionId": 27910720,
"collectionTitle": "Large Prints",
"creatorName": "Hirawat",
"models": [
{
"success": true,
"modelName": "Borsa Summer (Style Bogg)",
"printTimeHours": 2.04,
"weightGrams": 140.2,
"estimatedCost": 120.0
}
],
"creditBalance": 7,
"creditsDeducted": 2
}
Upload & Estimate
Accepts a 3D geometry file (.stl or .3mf) via multipart/form-data, parses slicing metadata, and returns a full cost breakdown. For .3mf files containing BambuStudio metadata, exact print times and filament weights are extracted directly — no slicing heuristics applied.
429 Too Many Requests.
Authorization: Bearer <key> or x-api-key: <key>). Anonymous requests return 401 — even when the server has no API_KEY configured.
usr_...) costs 1 credit per successful estimate — the deduction happens after parsing and pricing succeed, so failed uploads are never charged. The master key is free. Returns 402 when the account is out of credits, and 403 (code: "email_not_verified") when the key's account has not verified its email address yet. If the debit cannot be recorded, the request fails closed with 503 (code: "credit_sync_failed") and withholds the estimate.
Request Body — multipart/form-data
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| file | File | required | — | .stl or .3mf file, max 15 MB. |
| qty | integer | optional | 1 | Print quantity. Used to calculate bulk plate-sharing capacity. |
| timeRate | float | optional | 50 | Hourly printer operational rate. |
| weightRate | float | optional | 2.0 | Filament cost per gram. |
| plateFee | float | optional | 20 | Setup fee per build plate run. |
| multicolorFee | float | optional | 30 | AMS filament-swap fee per colour change. |
| labourFee | float | optional | 0 | Flat-rate manual handling charge. |
| energyRate | float | optional | 0 | Electricity price per kWh. Adds an energy cost line, (power / 1000) × print time × kWh price. |
| printerPower | float | optional | 0 | Average printer power draw in watts, used with energyRate. |
| profitMargin | float | optional | 0 | Profit margin percentage added on top of subtotal. |
| roundingFactor | float | optional | 10 | Round final cost to the nearest n. |
| currencySymbol | string | optional | "Rs." | Symbol embedded in formula output string. |
| currency | string | optional | "" | Optional 3-letter currency code (e.g., USD, EUR, JPY) to resolve the exchange rate. |
Request
curl -X POST \
https://api.tryar.in/api/estimate/upload \
-F "file=@./my_model.stl" \
-F "qty=5" \
-F "timeRate=60" \
-F "weightRate=2.5" \
-F "currencySymbol=₹"
JavaScript
const fd = new FormData();
fd.append('file', fileInput.files[0]);
fd.append('qty', '5');
fd.append('timeRate', '60');
const res = await fetch(
'https://api.tryar.in/api/estimate/upload',
{ method: 'POST', body: fd }
);
const data = await res.json();
console.log(data.pricing.estimatedCost);
Response 200 OK
{
"success": true,
"filename": "my_model.stl",
"type": "STL",
"parsedData": {
"weightGrams": 45.2,
"printTimeHours": 1.25,
"printTimeSeconds":4500,
"widthMm": 50.5,
"lengthMm": 45.0,
"heightMm": 22.0,
"isFromMetadata": false,
"filaments": [
{
"type": "PLA",
"color": "#10b981",
"usedG": 45.2,
"name": "Emerald Green",
"multiplier": 1
}
]
},
"pricing": {
"qty": 5,
"estimatedCost": 160.0,
"currencySymbol": "₹",
"totalTimeCost": 75.0,
"totalMaterialCost": 90.4,
"plateCost": 0,
"subtotal": 152.9,
"formula": "5 unit(s) · 1.25h · ₹160",
"savings": 0
},
"creditBalance": 9,
"creditsDeducted": 1
}
GET /api/credits
Returns the signed-in user's credit balance and plan. Billing fields are read with the server token, so the response is authoritative even though the client record hides them.
Authentication
Requires the user's PocketBase session token (Authorization: Bearer <token>) and a matching userId query parameter (401 otherwise).
GET /api/credits/history
Lists the newest credit-ledger rows for the signed-in user, newest first. Use limit (default 50) to page the history.
Response
{ transactions: [{ type, amount, balance_after, source, model_name, created }] }. Requires the same session token and userId as /api/credits.
POST /api/credits/deduct
Deducts calculation credits (default 1, up to amount) from the signed-in user. The web app calls this before a local upload estimate. The deduction is atomic (optimistic-locked read-modify-write with retry), so concurrent calls cannot overspend the balance. The debit ledger row is written before the response — an uncharged deduction is never reported as success.
Authentication & errors
Requires the user's PocketBase session token (Authorization: Bearer <token>) matching userId (401 otherwise) and a verified email address (403, code: "email_not_verified"). Returns 402 when the balance is insufficient (the body carries a creditsInfo snapshot) and 503 (code: "credit_sync_failed") when the debit cannot be recorded — retry instead of assuming success.
{
"success": true,
"credits": 9,
"plan": "free"
}
GET POST /api/user/presets
Fetch or save custom pricing defaults (time rate, weight rate, plate fee, multicolor fee, labour fee, profit margin, currency) for a logged-in user.
Authentication
Requires the user's PocketBase session token (Authorization: Bearer <token>) matching userId. Returns 401 when the token is missing, invalid, or belongs to a different user.
{
"success": true,
"presets": {
"timeRate": 50,
"weightRate": 2,
"plateFee": 20,
"multicolorFee": 30,
"labourFee": 0,
"profitMargin": 0,
"currency": "INR"
}
}
GET POST /api/user/api-key
Retrieve or generate a user-scoped API Key (usr_...) for authenticating programmatic requests to /api/scrape and /api/estimate/upload. Keys are stored as SHA-256 hashes, so the raw value is returned only when a key is generated: GET on an account that already has one responds with apiKey: null and keyHidden: true — use POST to rotate and reveal a new key.
Authentication
Requires the user's PocketBase session token (Authorization: Bearer <token>) matching userId — third parties cannot fetch or regenerate another user's key. Returns 401 when the token is missing, invalid, or belongs to a different user; 403 on the Pro plan gate.
{
"success": true,
"apiKey": "usr_a1b2c3d4e5f67890",
"keyHidden": false
}
POST /api/auth/send-verification
Sends (or resends) the account verification email through the self-hosted Listmonk instance. The email contains a single-use link to /verify-email?token=… that expires after 24 hours; only a SHA-256 hash of the token is stored.
Authentication & anti-enumeration
Authenticated calls — send the user's PocketBase session token (Authorization: Bearer <token>) with the matching userId — act on the signed-in account. A 60-second resend cooldown returns 429 with retryAfter, and 503 is returned when the email service is not configured. Anonymous calls accept an email and always return a generic 200, so account existence cannot be probed.
curl -X POST https://api.tryar.in/api/auth/send-verification \
-H "Authorization: Bearer <pb-session-token>" \
-H "X-User-Id: usr_abc123" \
-H "Content-Type: application/json" \
-d '{"userId":"usr_abc123"}'
{
"success": true,
"sent": true
}
POST /api/auth/verify-email
Redeems the token from the verification email link (called by the /verify-email landing page). On success the PocketBase users.verified flag is set, the single-use token is cleared, and — in the background — opted-in accounts are subscribed to the onboarding Listmonk list and sent the welcome email. Unverified accounts can sign in but calculation endpoints (/api/scrape, /api/scrape/collection, /api/credits/deduct, /api/estimate/upload, MCP tools) return 403 with code: "email_not_verified". Product-update emails can be opted out of anytime via /api/user/email-preferences.
curl -X POST https://api.tryar.in/api/auth/verify-email \
-H "Content-Type: application/json" \
-d '{"token":"Xr9k…"}'
{
"success": true,
"email": "[email protected]",
"alreadyVerified": false
}
Errors
400 — missing or invalid token (code: "invalid_token"). 410 — token older than 24 hours (code: "expired_token"); request a fresh link from the app.
POST /api/user/email-preferences
Opts the signed-in account in or out of product-update emails (onboarding tips, feature announcements, offers). The choice is stored on the account (users.marketing_opt_in) and synced to the Listmonk onboarding list — opted in subscribes, opted out unsubscribes. The Listmonk sync is best-effort, so the user's choice is saved even if the mail service is briefly unavailable. Users can also change this from the Email Preferences card on their account page; account emails such as verification links and receipts are always delivered.
Authentication
Requires the user's PocketBase session token (Authorization: Bearer <token>) matching userId; returns 401 otherwise and 400 when marketingOptIn is not a boolean.
curl -X POST https://api.tryar.in/api/user/email-preferences \
-H "Authorization: Bearer <pb-session-token>" \
-H "Content-Type: application/json" \
-d '{"userId":"usr_abc123","marketingOptIn":false}'
{
"success": true,
"marketingOptIn": false,
"listSynced": true
}
POST /api/payments/checkout
Creates a Dodo Payments checkout session for the Pro subscription (pro_monthly, pro_yearly) or the 100-credit top-up pack (pack_100). The body carries productId (or packId), quantity, userId, and optional success/cancel URLs and customer email. The product id is mapped server-side, so clients never pass raw Dodo ids.
Authentication
An account is required (userId); the session is attributed through checkout metadata. Payment state is applied only after a signed webhook or a verified checkout return.
GET POST /api/mcp
Model Context Protocol (JSON-RPC 2.0) streamable HTTP server for AI assistants including ChatGPT, Claude Desktop, Cursor, Claude Code CLI, Windsurf, and custom autonomous agents. ChatGPT connects through OAuth 2.1 (no API key needed in the UI); CLI clients can keep using an API key. For an interactive tester and 1-click configs, visit the MCP Developer Hub.
Transport rules
Notifications (requests without an id) return 202 with no body. JSON-RPC errors use HTTP 200 with an error object. Auth and credit failures use 401/402/403. A protected tool without a usable credential answers 401 with a WWW-Authenticate header; the body also carries _meta["mcp/www_authenticate"], so ChatGPT can refresh the token or open its account-linking UI. Batches are capped at 20 messages and bodies at 24 MB. The server speaks protocol versions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.
Exposed Tools
estimate_print_cost(Auth / Credits) — Goal-oriented tool for ChatGPT: takes a model URL, quantity and currency, then returns a priced breakdown plus the estimate card. Costs 1 credit.scrape_model(Auth / Credits) — Scrapes specifications and per-plate estimates for a supported platform URL (MakerWorld, Printables, Thingiverse, Cults3D). Requires a user API key or OAuth token; deducts 1 credit per successful estimate (metadata-only responses are free).list_models(Public) — Searches previously calculated models in the catalog.search/fetch(Public) — Company-knowledge pair: search the directory, then fetch one entry as text with its source URL.get_profile(Auth) — Returns the linked account's opaque profile id, name and email. Marked as the profile tool for multi-account support.get_leaderboard(Public) — Retrieves top all-time popular and 7-day trending models.get_presets(Public) — Fetches standard filament color multipliers and hardware keywords.detect_currency(Public) — Detects regional currency code based on Cloudflare geo-IP.estimate_file(Auth) — Parses base64 STL/3MF files and calculates detailed cost breakdowns. Requires an API key or OAuth token; the decoded file must be ≤ 15 MB.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "scrape_model",
"arguments": {
"url": "https://makerworld.com/en/models/1717122"
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\"title\":\"Statue of Liberty\",\"totalGrams\":45.2,\"estimatedCost\":120}"
}
]
}
}
GET /.well-known/oauth-protected-resource
ChatGPT reads this document before it asks the user to connect an account. It names the protected resource (this API), the authorization server, and the scopes it may request. The authorization-server metadata at /.well-known/oauth-authorization-server advertises the endpoints, PKCE S256 support, public clients (token_endpoint_auth_method: none) and RFC 9207 issuer identification.
Why OAuth instead of an API key
ChatGPT cannot send a custom API-key header from its connector UI. OAuth lets each user link their own Estimator account, so calculations charge their own credits and the existing email-verification gate still applies. API keys keep working for CLI and server clients.
{
"resource": "https://api.tryar.in",
"authorization_servers": ["https://api.tryar.in"],
"scopes_supported": ["estimate"],
"bearer_methods_supported": ["header"]
}
POST /api/oauth/*
Authorization Code + PKCE. Access tokens are opaque mcp_at_… values (1 hour); refresh tokens rotate (30 days). Only SHA-256 hashes are stored.
Connector URL for ChatGPT: https://api.tryar.in/api/mcp. Step-by-step setup is on the MCP Developer Hub.
Response Status Codes
Standard HTTP status codes indicate the outcome of each request.