Version: 1.2.0
Last Updated: March 29, 2026
Welcome to the PhoneTea Developer API. This REST API provides programmatic access to our comprehensive phone number review database, spam detection system, and analytics platform.
Before using the PhoneTea API, you need:
Important: Free accounts do not have API access. Upgrade to Premium to get started with 500-2,500 free API calls per month.
Production: https://us-east1-{your-project-id}.cloudfunctions.net/api
All API requests require a Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_KEY
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://us-east1-{project-id}.cloudfunctions.net/api/v1/phone/lookup?number=5551234567"
For Premium Users:
For API Subscription Users (Additional Capacity):
| Type | Purpose | Rate Limit | Best For |
|---|---|---|---|
| Production | Live applications | Based on subscription tier | Production apps |
| Sandbox | Testing & development | 100/day | Development & testing |
| Enterprise | High-volume approved | Custom limits | Large integrations |
| Event | What Happens |
|---|---|
| Subscribe to Premium | API access granted β create your key manually |
| Purchase API Subscription | Your quota increases (Premium + API tier combined) |
| Upgrade API Tier | Key limit increased immediately |
| Downgrade API Tier | Key limit reduced at end of billing period |
| Cancel API Subscription | Key reverts to Premium-only quota |
| Cancel Premium | All keys deactivated, API access revoked |
| Refund Premium | Immediate deactivation of all keys |
// Node.js Example
const headers = {
'Authorization': `Bearer ${YOUR_API_KEY}`,
'Content-Type': 'application/json'
};
fetch('https://api-endpoint/v1/phone/lookup?number=5551234567', { headers })
.then(res => res.json())
.then(data => console.log(data));
// β DON'T: Hardcode keys in source code
const apiKey = 'pt_prod_1234567890abcdef';
// β
DO: Use environment variables
const apiKey = process.env.PHONETEA_API_KEY;
// β DON'T: Expose keys in client-side JavaScript
// β
DO: Proxy requests through your backend server
Premium subscribers get free API calls each month as part of their subscription:
| Premium Tier | Monthly API Quota | Subscription Price | API Discount |
|---|---|---|---|
| Free | 0 (No API access) | $0 | - |
| Monthly Pro | 500 calls/month | $9.99/mo | 10% off API tiers |
| Yearly Pro | 1,000 calls/month | $99.99/yr | 15% off API tiers |
| Lifetime Pro | 2,500 calls/month | $499.99 (one-time) | 20% off API tiers |
Note: Premium subscription is required for any API access. Free users cannot use the API.
For higher volume needs, Premium users can purchase an additional API subscription:
| API Tier | Monthly Limit | Price | Cost per Call | Approval |
|---|---|---|---|---|
| Pay-As-You-Go | Unlimited | $0/mo base | $0.03/call | Instant |
| Starter | 5,000 calls/mo | $29/mo | $0.0058 | Instant |
| Professional | 20,000 calls/mo | $79/mo | $0.0040 | Instant |
| Business | 75,000 calls/mo | $199/mo | $0.0027 | Instant |
| Enterprise | 250,000 calls/mo | $499/mo | $0.0020 | 24-48 hours |
| Custom | 250,000+ | Contact Sales | Volume rates | Required |
Your total monthly API limit = Premium quota + API subscription quota
Example:
Yearly Pro Premium: 1,000 calls/month (included free)
+ Starter API Tier: + 5,000 calls/month
βββββββββββββββββββββββββββββββββββββββββ
= Total Available: 6,000 calls/month
Consumption Order: Your included Premium calls are always consumed first. Once exhausted, calls draw from your purchased API subscription quota. For PAYG users, included calls are free β billing at $0.03/call starts only after included calls are used up.
Your two quota pools reset independently:
| Quota Type | Resets On |
|---|---|
| Included (Premium) | 1st of every month at 00:00 UTC |
| Purchased (API) | Your Stripe billing date (subscription renewal) |
Unused calls do not roll over to the next period.
Premium subscribers receive discounts on API subscription tiers:
| Premium Tier | Base Discount | Business Verified Bonus | Max Discount |
|---|---|---|---|
| Monthly Pro | 10% off | +5% | 15% |
| Yearly Pro | 15% off | +5% | 20% |
| Lifetime Pro | 20% off | +5% | 25% |
Maximum combined discount capped at 30%
Example Pricing with Discount:
Professional Tier: $79/mo
Yearly Pro Discount: 15% off
βββββββββββββββββββββββββββββ
Your Price: $67.15/mo (save $11.85)
Pay-As-You-Go (PAYG) is perfect for variable usage or testing before committing to a fixed tier.
PAYG requests include special billing headers:
X-Billing-Mode: pay_as_you_go
X-Usage-ThisMonth: 1547
X-Included-Quota: 1000
X-Included-Used: 1000
X-Billable-Calls: 547
X-Cost-Per-Request: 0.03
X-Estimated-Monthly-Cost: 16.4100
X-Usage-Reset: 1735689600
| Header | Description |
|---|---|
X-Billing-Mode |
Always pay_as_you_go for PAYG keys |
X-Usage-ThisMonth |
Total API calls this billing cycle |
X-Included-Quota |
Your included Premium calls (free tier) |
X-Included-Used |
Included calls consumed (not billed) |
X-Billable-Calls |
Calls beyond included quota (billed at $0.03) |
X-Cost-Per-Request |
Cost per billable API call ($0.03) |
X-Estimated-Monthly-Cost |
Projected cost for billable calls only |
X-Usage-Reset |
Unix timestamp when billing cycle resets |
curl -I -H "Authorization: Bearer YOUR_PAYG_API_KEY" \
"https://us-east1-project.cloudfunctions.net/api/v1/phone/lookup?number=5551234567"
# Response Headers:
# X-Billing-Mode: pay_as_you_go
# X-Usage-ThisMonth: 1248
# X-Included-Quota: 1000
# X-Included-Used: 1000
# X-Billable-Calls: 248
# X-Cost-Per-Request: 0.03
# X-Estimated-Monthly-Cost: 7.4400
Batch requests are billed per phone number/review processed, not per HTTP request.
| Request Type | Billing |
|---|---|
| Single phone lookup | 1 API call |
| Batch lookup (50 numbers) | 50 API calls |
| Single review submit | 1 API call |
| Batch review submit (25 reviews) | 25 API calls |
Before processing batch requests, the API validates you have sufficient quota:
// Request: Batch lookup with 50 numbers
// Your remaining quota: 30 calls
{
"success": false,
"error": {
"code": "insufficient_quota",
"message": "This request requires 50 units but you only have 30 remaining",
"details": {
"requested": 50,
"available": 30,
"upgrade_url": "https://phonetea.com/api-pricing"
}
}
}
Per-operation billing ensures fair pricing:
This applies to all plans including Pay-As-You-Go.
Every API response includes rate limit or billing information.
X-RateLimit-Limit: 6000
X-RateLimit-Remaining: 4950
X-RateLimit-Used: 1050
X-RateLimit-Requested: 1
X-Included-Quota: 1000
X-Included-Used: 1000
X-Purchased-Quota: 5000
X-Purchased-Used: 50
X-RateLimit-Reset: 1735689600
| Header | Description |
|---|---|
X-RateLimit-Limit |
Your total monthly API call limit (included + purchased) |
X-RateLimit-Remaining |
Calls remaining this month (across both quotas) |
X-RateLimit-Used |
Calls used this month (across both quotas) |
X-RateLimit-Requested |
Units this request will consume |
X-Included-Quota |
Your included Premium calls quota |
X-Included-Used |
Included calls consumed this period |
X-Purchased-Quota |
Your purchased API subscription quota |
X-Purchased-Used |
Purchased calls consumed this period |
X-RateLimit-Reset |
Unix timestamp when limit resets |
X-Billing-Mode: pay_as_you_go
X-Usage-ThisMonth: 1547
X-Cost-Per-Request: 0.03
X-Estimated-Monthly-Cost: 15.4700
X-Usage-Reset: 1735689600
When you exceed your rate limit (fixed tiers only):
{
"success": false,
"error": {
"code": "rate_limit_exceeded",
"message": "Monthly API limit exceeded",
"details": {
"limit": 6000,
"used": 6000,
"included_quota": 1000,
"included_used": 1000,
"purchased_quota": 5000,
"purchased_used": 5000,
"resets_at": "2025-02-01T00:00:00Z",
"upgrade_url": "https://phonetea.com/api-pricing"
}
}
}
Best Practice: Monitor X-RateLimit-Remaining and implement exponential backoff:
async function apiCallWithRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fn();
// Check remaining quota
const remaining = response.headers.get('X-RateLimit-Remaining');
if (remaining && parseInt(remaining) < 100) {
console.warn(`Low quota warning: ${remaining} calls remaining`);
}
return response;
} catch (error) {
if (error.status === 429 && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await new Promise(r => setTimeout(r, delay));
continue;
}
throw error;
}
}
}
PhoneTea uses labels instead of traditional star ratings to categorize phone numbers:
| Label | Icon | Meaning | Spam Impact |
|---|---|---|---|
trollSpamFake |
β οΈ | Warning - Spam/Scam/Fake number | Increases spam score |
telemarketer |
π | Warning - Marketing/Sales calls | Increases spam score |
realPersonFakeNumber |
β οΈ | Caution - Person using fake identity | Neutral |
realPersonRealNumber |
β | Safe - Legitimate person | Neutral |
professional |
πΌ | Safe - Business/Professional | Neutral |
The spam score (0-100) is calculated dynamically:
spam_score = (spam_review_count / total_reviews) Γ 100
Where spam_review_count = count of (trollSpamFake + telemarketer)
Examples:
trollSpamFake β 80% spam scoretelemarketer β 30% spam score| Spam Score | Risk Level | Recommendation |
|---|---|---|
| 70-100 | high |
Block or warn strongly |
| 40-69 | medium |
Show caution |
| 0-39 | low |
Safe to answer |
| Category | Endpoint | Method | Purpose | Billing |
|---|---|---|---|---|
| Core | /v1/phone/lookup |
GET | Get full phone number data | 1 call |
| Core | /v1/phone/validate |
POST | Validate phone number | 1 call |
| Core | /v1/phone/spam-score |
GET | Get spam score only | 1 call |
| Core | /v1/review/submit |
POST | Submit a review | 1 call |
| Search | /v1/search |
GET | Search by criteria | 1 call |
| User | /v1/user/usage |
GET | Get API usage stats | 0 calls (free) |
| Health | /v1/status |
GET | Health check | 0 calls (free) |
| Batch | /v1/phone/lookup/batch |
POST | Batch phone lookup | N calls (N = numbers count) |
| Batch | /v1/review/submit/batch |
POST | Batch review submit | N calls (N = reviews count) |
| Analytics | /v1/analytics/trending |
GET | Get trending numbers | 1 call |
| Analytics | /v1/analytics/spam-trends |
GET | Get spam patterns | 1 call |
| Analytics | /v1/analytics/top-spam |
GET | Get top spam numbers | 1 call |
| Analytics | /v1/analytics/review-stats |
GET | Get review statistics | 1 call |
Get comprehensive information about a phone number including spam score, reviews, carrier data, and location.
Endpoint: GET /v1/phone/lookup
Billing: 1 API call
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
number |
string | Yes | Phone number (any format accepted) |
Request:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://us-east1-project.cloudfunctions.net/api/v1/phone/lookup?number=5551234567"
Success Response (200):
{
"success": true,
"data": {
"number": "5551234567",
"formatted_number": "(555) 123-4567",
"spam_score": 75,
"review_count": 12,
"most_common_label": "trollSpamFake",
"risk_level": "high",
"phone_type": "mobile",
"carrier": "Verizon Wireless",
"is_verified": false,
"city": "Los Angeles",
"state": "CA",
"area_code": "555",
"reviews": [
{
"id": "rev_abc123",
"content": "Spam caller trying to sell insurance",
"label": "trollSpamFake",
"username": "JohnDoe",
"created_at": "2025-01-15T10:30:00.000Z",
"is_owner_comment": false
}
]
}
}
Not Found Response (200):
{
"success": true,
"data": {
"number": "5551234567",
"spam_score": 0,
"review_count": 0,
"risk_level": "low",
"most_common_label": null,
"reviews": []
}
}
Validate phone number format and retrieve carrier information.
Endpoint: POST /v1/phone/validate
Billing: 1 API call
Request Body:
{
"number": "5551234567"
}
Success Response (200):
{
"success": true,
"data": {
"number": "5551234567",
"valid": true,
"carrier": "Verizon Wireless",
"phone_type": "mobile",
"is_voip": false,
"region": "US"
}
}
Get just the spam score for a number (lightweight, faster than full lookup).
Endpoint: GET /v1/phone/spam-score
Billing: 1 API call
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
number |
string | Yes | Phone number to check |
Success Response (200):
{
"success": true,
"data": {
"number": "5551234567",
"spam_score": 75,
"most_common_label": "trollSpamFake",
"risk_level": "high"
}
}
Submit a review for a phone number via API.
Endpoint: POST /v1/review/submit
Billing: 1 API call
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
number |
string | Yes | Phone number |
content |
string | Yes | Review text |
label |
string | Yes | Review label (see valid labels below) |
author |
string | No | Author name (defaults to βAPI Userβ) |
Valid Labels:
trollSpamFaketelemarketerrealPersonFakeNumberrealPersonRealNumberprofessionalRequest:
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"number": "5551234567",
"content": "Spam caller selling insurance",
"label": "trollSpamFake",
"author": "My Call Blocker App"
}' \
"https://us-east1-project.cloudfunctions.net/api/v1/review/submit"
Success Response (200):
{
"success": true,
"data": {
"review_id": "rev_xyz789",
"message": "Review submitted successfully",
"number": "5551234567",
"label": "trollSpamFake"
}
}
Search for phone numbers by area code, city, state, or number prefix.
Endpoint: GET /v1/search
Billing: 1 API call
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Search query |
type |
string | No | Search type: number, area_code, city, state |
limit |
integer | No | Max results (default: 10, max: 50) |
Request Examples:
# Search by area code
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://api.../v1/search?query=555&type=area_code&limit=20"
# Search by city
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://api.../v1/search?query=Los%20Angeles&type=city"
Success Response (200):
{
"success": true,
"data": {
"query": "555",
"type": "area_code",
"total_results": 15,
"results": [
{
"number": "5551234567",
"city": "Los Angeles",
"state": "CA",
"review_count": 12,
"spam_score": 75
}
]
}
}
Get your API usage statistics and rate limit information.
Endpoint: GET /v1/user/usage
Billing: Free (0 API calls)
Success Response (200):
{
"success": true,
"data": {
"api_key_name": "Production API Key",
"key_type": "production",
"plan": "starter",
"billing_mode": "fixed",
"usage": {
"today": 145,
"this_month": 3842,
"included_quota": 1000,
"included_used": 1000,
"included_remaining": 0,
"purchased_quota": 5000,
"purchased_used": 2842,
"purchased_remaining": 2158,
"total_limit": 6000,
"total_remaining": 2158,
"key_calls_today": 45,
"key_calls_this_month": 1200
},
"subscription": {
"premium_tier": "yearly",
"api_tier": "starter",
"discount_applied": 15,
"next_billing_date": "2025-02-01T00:00:00Z"
},
"last_30_days": {
"total_calls": 4123,
"successful": 4098,
"failed": 25,
"by_endpoint": {
"/v1/phone/lookup": { "successful": 2500, "failed": 10 },
"/v1/phone/spam-score": { "successful": 1200, "failed": 5 },
"/v1/review/submit": { "successful": 398, "failed": 10 }
}
},
"created_at": "2024-12-01T00:00:00.000Z",
"last_used": "2025-01-15T14:23:10.000Z"
}
}
PAYG Usage Response:
{
"success": true,
"data": {
"api_key_name": "Production API Key",
"key_type": "production",
"plan": "payAsYouGo",
"billing_mode": "pay_as_you_go",
"usage": {
"today": 45,
"this_month": 1547,
"included_quota": 1000,
"included_used": 1000,
"included_remaining": 0,
"purchased_quota": "unlimited",
"purchased_used": 547,
"purchased_remaining": "unlimited",
"total_limit": "unlimited",
"total_remaining": "unlimited",
"cost_this_month": 16.41,
"cost_per_call": 0.03
},
"subscription": {
"premium_tier": "lifetime",
"api_tier": "payAsYouGo",
"discount_applied": 20,
"next_billing_date": "2025-02-01T00:00:00Z"
}
}
}
Check API status (no authentication required).
Endpoint: GET /v1/status
Billing: Free (0 API calls, no auth required)
Success Response (200):
{
"success": true,
"status": "operational",
"version": "1.1.0",
"time": "2025-12-16T14:30:00.000Z"
}
Look up multiple phone numbers in a single request.
Endpoint: POST /v1/phone/lookup/batch
Billing: N API calls (where N = number of phone numbers)
Limits:
Request:
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"numbers": ["5551234567", "5559876543", "5555555555"]}' \
"https://us-east1-project.cloudfunctions.net/api/v1/phone/lookup/batch"
Success Response (200):
{
"success": true,
"data": {
"total_requested": 3,
"successful": 3,
"failed": 0,
"billing": {
"calls_consumed": 3,
"remaining_quota": 4997
},
"results": [
{
"number": "5551234567",
"formatted_number": "(555) 123-4567",
"spam_score": 75,
"review_count": 12,
"most_common_label": "trollSpamFake",
"risk_level": "high"
},
{
"number": "5559876543",
"formatted_number": "(555) 987-6543",
"spam_score": 20,
"review_count": 3,
"most_common_label": "professional",
"risk_level": "low"
},
{
"number": "5555555555",
"formatted_number": "(555) 555-5555",
"spam_score": 0,
"review_count": 0,
"most_common_label": null,
"risk_level": "low"
}
]
}
}
Insufficient Quota Response (402):
{
"success": false,
"error": {
"code": "insufficient_quota",
"message": "This request requires 100 units but you only have 50 remaining",
"details": {
"requested": 100,
"available": 50,
"upgrade_url": "https://phonetea.com/api-pricing"
}
}
}
Submit multiple reviews in a single request.
Endpoint: POST /v1/review/submit/batch
Billing: N API calls (where N = number of reviews)
Limits:
Request:
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reviews": [
{"number": "5551234567", "content": "Spam caller", "label": "trollSpamFake"},
{"number": "5559876543", "content": "Legit business", "label": "professional"}
]
}' \
"https://us-east1-project.cloudfunctions.net/api/v1/review/submit/batch"
Success Response (200):
{
"success": true,
"data": {
"total_requested": 2,
"successful": 2,
"failed": 0,
"billing": {
"calls_consumed": 2,
"remaining_quota": 4998
},
"results": [
{"index": 0, "review_id": "rev_abc123", "number": "5551234567", "label": "trollSpamFake"},
{"index": 1, "review_id": "rev_xyz789", "number": "5559876543", "label": "professional"}
],
"message": "Successfully submitted 2 reviews"
}
}
Partial Success Response (200):
{
"success": true,
"data": {
"total_requested": 3,
"successful": 2,
"failed": 1,
"billing": {
"calls_consumed": 2,
"remaining_quota": 4998
},
"results": [
{"index": 0, "review_id": "rev_abc123", "number": "5551234567", "label": "trollSpamFake"},
{"index": 2, "review_id": "rev_def456", "number": "5555555555", "label": "professional"}
],
"errors": [
{"index": 1, "number": "5559876543", "error": "Invalid label provided"}
],
"message": "Successfully submitted 2 reviews"
}
}
Get the most searched or reviewed phone numbers in a time period.
Endpoint: GET /v1/analytics/trending
Billing: 1 API call
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit |
integer | No | 20 | Max results (max: 100) |
period |
string | No | 7d |
Time period: 24h, 7d, 30d |
Success Response (200):
{
"success": true,
"data": {
"period": "7d",
"total_results": 10,
"results": [
{
"number": "5551234567",
"formatted_number": "(555) 123-4567",
"recent_reviews": 45,
"total_reviews": 127,
"spam_score": 85,
"most_common_label": "trollSpamFake",
"risk_level": "high",
"city": "Los Angeles",
"state": "CA"
}
]
}
}
Get spam patterns and trends over time.
Endpoint: GET /v1/analytics/spam-trends
Billing: 1 API call
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
period |
string | No | 30d |
Time period: 7d, 30d, 90d |
Success Response (200):
{
"success": true,
"data": {
"period": "30d",
"group_by": "day",
"overall_stats": {
"total_reviews": 5432,
"spam_reviews": 3215,
"spam_rate": 59,
"label_distribution": {
"trollSpamFake": 2105,
"telemarketer": 1110,
"realPersonFakeNumber": 487,
"realPersonRealNumber": 1230,
"professional": 500
}
},
"time_series": [
{
"date": "2025-10-16",
"total_reviews": 180,
"spam_reviews": 110,
"spam_percentage": 61
}
]
}
}
Get phone numbers with the highest spam scores.
Endpoint: GET /v1/analytics/top-spam
Billing: 1 API call
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit |
integer | No | 20 | Max results (max: 100) |
min_reviews |
integer | No | 3 | Minimum reviews required |
Success Response (200):
{
"success": true,
"data": {
"total_results": 10,
"min_reviews": 5,
"results": [
{
"number": "5551234567",
"formatted_number": "(555) 123-4567",
"spam_score": 95,
"review_count": 48,
"most_common_label": "trollSpamFake",
"risk_level": "high",
"city": "Los Angeles",
"state": "CA"
}
]
}
}
Get distribution and breakdown of review labels over time.
Endpoint: GET /v1/analytics/review-stats
Billing: 1 API call
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
period |
string | No | 30d |
Time period: 24h, 7d, 30d, 90d, all |
Success Response (200):
{
"success": true,
"data": {
"period": "30d",
"total_reviews": 5432,
"unique_numbers": 2187,
"spam_reviews": 3215,
"spam_percentage": 59,
"label_distribution": [
{"label": "trollSpamFake", "count": 2105, "percentage": 39},
{"label": "telemarketer", "count": 1110, "percentage": 20},
{"label": "realPersonRealNumber", "count": 1230, "percentage": 23},
{"label": "professional", "count": 500, "percentage": 9},
{"label": "realPersonFakeNumber", "count": 487, "percentage": 9}
]
}
}
| Code | Meaning | Description |
|---|---|---|
| 200 | OK | Request successful |
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 402 | Payment Required | Insufficient quota for batch request |
| 403 | Forbidden | Premium subscription required |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error |
All errors follow this format:
{
"success": false,
"error": {
"code": "error_code",
"message": "Human-readable error message",
"details": { }
}
}
| Error Code | HTTP Status | Description |
|---|---|---|
unauthorized |
401 | Invalid or missing API key |
premium_required |
403 | API access requires Premium subscription |
rate_limit_exceeded |
429 | Monthly rate limit exceeded |
insufficient_quota |
402 | Not enough quota for batch request |
invalid_request |
400 | Missing or invalid parameters |
invalid_label |
400 | Invalid review label provided |
internal_error |
500 | Server error |
401 Unauthorized:
{
"success": false,
"error": {
"code": "unauthorized",
"message": "Invalid API key"
}
}
403 Premium Required:
{
"success": false,
"error": {
"code": "premium_required",
"message": "API access requires an active Premium subscription",
"details": {
"upgrade_url": "https://phonetea.com/premium"
}
}
}
429 Rate Limit Exceeded:
{
"success": false,
"error": {
"code": "rate_limit_exceeded",
"message": "Monthly API limit exceeded",
"details": {
"limit": 6000,
"used": 6000,
"included_quota": 1000,
"included_used": 1000,
"purchased_quota": 5000,
"purchased_used": 5000,
"resets_at": "2025-02-01T00:00:00Z",
"upgrade_url": "https://phonetea.com/api-pricing"
}
}
}
402 Insufficient Quota:
{
"success": false,
"error": {
"code": "insufficient_quota",
"message": "This request requires 50 units but you only have 30 remaining",
"details": {
"requested": 50,
"available": 30,
"upgrade_url": "https://phonetea.com/api-pricing"
}
}
}
You can upgrade your API subscription at any time:
Effect: Changes take effect immediately. Your API key limits are updated instantly.
Effect: Downgrade takes effect at the end of your current billing period. You keep your current limits until then.
| Action | What Happens |
|---|---|
| Cancel API Subscription (Keep Premium) | API subscription ends at period end. Key reverts to Premium-only quota (500-2,500). Key remains active. |
| Cancel Premium Subscription | Immediately cancels both Premium AND any API subscription. All API keys deactivated. All API access revoked. |
| Premium Refund | Immediate full downgrade. All API keys deactivated. Same as cancellation but instant. |
| API Subscription Refund | Loses API tier quota. Keeps Premium status and quota. Key updated with reduced limits. |
class PhoneTeaAPI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://us-east1-{project-id}.cloudfunctions.net/api';
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers
};
const response = await fetch(url, { ...options, headers });
const data = await response.json();
// Log rate limit info
const remaining = response.headers.get('X-RateLimit-Remaining');
if (remaining && parseInt(remaining) < 100) {
console.warn(`β οΈ Low quota: ${remaining} calls remaining`);
}
return data;
}
async lookup(phoneNumber) {
return this.request(`/v1/phone/lookup?number=${phoneNumber}`);
}
async getSpamScore(phoneNumber) {
return this.request(`/v1/phone/spam-score?number=${phoneNumber}`);
}
async submitReview(phoneNumber, content, label, author = 'API User') {
return this.request('/v1/review/submit', {
method: 'POST',
body: JSON.stringify({ number: phoneNumber, content, label, author })
});
}
async batchLookup(numbers) {
return this.request('/v1/phone/lookup/batch', {
method: 'POST',
body: JSON.stringify({ numbers })
});
}
async getUsage() {
return this.request('/v1/user/usage');
}
}
// Usage
const api = new PhoneTeaAPI(process.env.PHONETEA_API_KEY);
// Single lookup
const result = await api.lookup('5551234567');
console.log(`Spam Score: ${result.data.spam_score}%`);
// Batch lookup (billed as 3 calls)
const batchResult = await api.batchLookup(['5551234567', '5559876543', '5555555555']);
console.log(`Processed ${batchResult.data.successful} numbers`);
// Check usage
const usage = await api.getUsage();
console.log(`Remaining: ${usage.data.usage.remaining} / ${usage.data.usage.total_limit}`);
import requests
from typing import List, Optional
class PhoneTeaAPI:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = 'https://us-east1-{project-id}.cloudfunctions.net/api'
self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
def _request(self, endpoint: str, method: str = 'GET', data: dict = None):
url = f'{self.base_url}{endpoint}'
if method == 'GET':
response = requests.get(url, headers=self.headers)
else:
response = requests.post(url, headers=self.headers, json=data)
# Check rate limits
remaining = response.headers.get('X-RateLimit-Remaining')
if remaining and int(remaining) < 100:
print(f"β οΈ Low quota warning: {remaining} calls remaining")
return response.json()
def lookup(self, phone_number: str):
return self._request(f'/v1/phone/lookup?number={phone_number}')
def get_spam_score(self, phone_number: str):
return self._request(f'/v1/phone/spam-score?number={phone_number}')
def submit_review(self, phone_number: str, content: str, label: str, author: str = 'API User'):
return self._request('/v1/review/submit', 'POST', {
'number': phone_number,
'content': content,
'label': label,
'author': author
})
def batch_lookup(self, numbers: List[str]):
return self._request('/v1/phone/lookup/batch', 'POST', {'numbers': numbers})
def get_usage(self):
return self._request('/v1/user/usage')
# Usage
import os
api = PhoneTeaAPI(os.environ['PHONETEA_API_KEY'])
# Single lookup
result = api.lookup('5551234567')
print(f"Spam Score: {result['data']['spam_score']}%")
# Batch lookup (billed as 3 calls)
batch_result = api.batch_lookup(['5551234567', '5559876543', '5555555555'])
print(f"Processed {batch_result['data']['successful']} numbers")
# Set your API key
export PHONETEA_API_KEY="your_api_key_here"
# Single lookup
curl -H "Authorization: Bearer $PHONETEA_API_KEY" \
"https://us-east1-project.cloudfunctions.net/api/v1/phone/lookup?number=5551234567"
# Spam score only (faster)
curl -H "Authorization: Bearer $PHONETEA_API_KEY" \
"https://us-east1-project.cloudfunctions.net/api/v1/phone/spam-score?number=5551234567"
# Submit review
curl -X POST \
-H "Authorization: Bearer $PHONETEA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"number":"5551234567","content":"Spam caller","label":"trollSpamFake"}' \
"https://us-east1-project.cloudfunctions.net/api/v1/review/submit"
# Batch lookup (billed as N calls)
curl -X POST \
-H "Authorization: Bearer $PHONETEA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"numbers":["5551234567","5559876543"]}' \
"https://us-east1-project.cloudfunctions.net/api/v1/phone/lookup/batch"
# Check usage (free)
curl -H "Authorization: Bearer $PHONETEA_API_KEY" \
"https://us-east1-project.cloudfunctions.net/api/v1/user/usage"
async function screenIncomingCall(phoneNumber) {
const api = new PhoneTeaAPI(process.env.PHONETEA_API_KEY);
// Quick spam check (1 API call)
const result = await api.getSpamScore(phoneNumber);
const { spam_score, risk_level } = result.data;
if (risk_level === 'high') {
return { action: 'BLOCK', reason: `High spam risk (${spam_score}%)` };
} else if (risk_level === 'medium') {
return { action: 'WARN', reason: `Medium risk (${spam_score}%)` };
}
return { action: 'ALLOW', reason: 'Low risk' };
}
async function analyzeContactList(contacts) {
const api = new PhoneTeaAPI(process.env.PHONETEA_API_KEY);
// Check quota first
const usage = await api.getUsage();
if (usage.data.usage.remaining < contacts.length) {
throw new Error(`Insufficient quota: need ${contacts.length}, have ${usage.data.usage.remaining}`);
}
// Process in batches of 100
const results = [];
for (let i = 0; i < contacts.length; i += 100) {
const batch = contacts.slice(i, i + 100);
const response = await api.batchLookup(batch);
results.push(...response.data.results);
}
// Find risky contacts
return results.filter(r => r.spam_score >= 40);
}
spam-score endpoint when full lookup isnβt needed/v1/user/usage endpointResponse Times:
Multi-Bucket Quota System:
API Key Management:
Quota Enforcement:
Pricing & Billing Updates:
New Features:
insufficient_quota error for batch requestspremium_required error for free usersInitial Release:
Built with β€οΈ by the PhoneTea Team
Β© 2025-2026 PhoneTea. All rights reserved.