• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
tr3ntg

tr3ntg

readback-api

API for readback.
Unlisted
Like
readback-api
Home
Code
12
.claude
1
backend
7
docs
1
marketing
3
test
1
.vtignore
ENVIRONMENT_CONFIG.md
README.md
deno.json
main.tsx
test-upload.html
test-url-extraction.html
Branches
2
Pull requests
Remixes
History
Environment variables
7
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
backend
/
README.md
Code
/
backend
/
README.md
Search
11/8/2025
README.md

Backend API

This backend provides a speech synthesis API with comprehensive usage tracking, multi-tier subscriptions, and non-expiring credit packs.

Structure

  • index.ts - Main Hono app with route definitions and webhook endpoints
  • middleware/auth.ts - User identification, authentication, and plan detection (RevenueCat + admin key)
  • middleware/usage.ts - Dual-bucket usage tracking, allocation, and rate limiting
  • database/usage.ts - Monthly usage tracking operations
  • database/credits.ts - Non-expiring credits ledger system
  • plans/config.ts - Central configuration for plans and credit packs

Features

Environment Configuration

  • Environment is configured per-request, not globally
  • Clients can specify environment using:
    • Header: X-Environment: SANDBOX or X-Environment: PRODUCTION
    • Query Parameter: ?environment=sandbox or ?environment=production
    • Default: PRODUCTION (when not specified)
  • Sandbox subscriptions and credits are completely isolated from production
  • All webhook events are stored with their actual environment from RevenueCat
  • Queries filter based on the request's environment parameter

Authentication

  • Admin access via ADMIN_ACCESS_KEY environment variable
  • Customer authentication via RevenueCat subscription verification
  • Authorization header format: Bearer {customer_id} or Customer {customer_id}
  • Automatic plan detection (Plus vs Pro) from RevenueCat entitlements
  • Subscription filtering based on per-request environment parameter

Credit System

Subscription Plans

  • Plus Plan: 900,000 characters per calendar month
  • Pro Plan: 2,700,000 characters per calendar month
  • Calendar month-based tracking (resets on 1st of each month)
  • Subscription credits expire at the end of each month

Non-Expiring Credits

  • Initial Free Grant: 45,000 characters for all users
  • Credit Packs: Purchasable packs of 25k, 100k, or 500k characters
  • Credits never expire and accumulate across purchases
  • Used only after subscription credits are exhausted

Credit Allocation

  1. Subscription credits are always used first (if available)
  2. Non-expiring credits are used when subscription is exhausted or unavailable
  3. Single requests can consume from both buckets if needed
  4. Requests are rejected when both buckets are empty

Database Schema

Table: customer_usage_v1

Tracks all character usage:

  • id - Auto-incrementing primary key
  • customer_id - Customer identifier from RevenueCat
  • character_count - Number of characters in the request
  • request_timestamp - ISO timestamp of the request
  • created_at - Database insertion timestamp

Table: credit_grants_v1

Ledger of all credit additions:

  • id - Auto-incrementing primary key
  • customer_id - Customer identifier
  • source - Type of grant (free_grant, iap, admin, refund)
  • product_lookup_key - RevenueCat product ID for purchases
  • revenuecat_tx_id - Transaction ID for idempotency
  • characters_granted - Number of characters granted (negative for refunds)
  • price_usd - Price paid for the credits
  • granted_at - Timestamp of grant
  • environment - Environment of the grant (SANDBOX or PRODUCTION)

Table: credit_debits_v1

Ledger of non-expiring credit consumption:

  • id - Auto-incrementing primary key
  • customer_id - Customer identifier
  • usage_id - Foreign key to customer_usage_v1
  • characters_consumed - Number of non-expiring characters used
  • consumed_at - Timestamp of consumption

API Endpoints

POST /api/speech

Converts text to speech using LemonFox API.

Headers:

  • Authorization: Bearer {customer_id} or Authorization: Bearer {admin_key}
  • X-Environment: SANDBOX (optional, defaults to PRODUCTION)

Query Parameters:

  • environment=sandbox (optional, alternative to header)

Body:

{ "voice": "voice_name", "input": "text to convert" }

Response:

{ "audio": "base64_encoded_audio", "word_timestamps": [ { "word": "hello", "start": 0.0, "end": 0.5 } ] }

GET /api/usage

Get comprehensive usage statistics and credit balances.

Headers:

  • Authorization: Bearer {customer_id} or Authorization: Customer {customer_id}
  • X-Environment: SANDBOX (optional, defaults to PRODUCTION)

Query Parameters:

  • environment=sandbox (optional, alternative to header)

Response (Plus/Pro users):

{ "user_tier": "premium", "monthly_limit": 900000, "current_usage": 150000, "remaining_characters": 750000, "usage_percentage": 17, "reset_date": "2024-02-01T00:00:00.000Z", "plan_renewal_date": "February 5, 2024", "plan_term": "monthly", "plan_term_in_days": 30, "plan_key": "plus", "plan_gross_cost": 2.99, "trialing": false, "credits": { "subscription": { "plan_key": "plus", "monthly_limit": 900000, "current_usage": 150000, "remaining_characters": 750000, "usage_percentage": 17, "reset_date": "2024-02-01T00:00:00.000Z" }, "non_expiring_tokens": { "balance": 70000, "total_granted": 95000, "total_consumed": 25000, "purchases": [ { "source": "free_grant", "characters": 45000, "price_usd": 0 }, { "source": "iap", "product_lookup_key": "credit_pack_1hr", "characters": 25000, "price_usd": 2.99 } ] } } }

Response (Free users):

{ "user_tier": "free", "lifetime_limit": 45000, "current_usage": 15000, "remaining_characters": 30000, "usage_percentage": 33, "message": "Subscribe for monthly credits or purchase additional credits.", "plan_renewal_date": null, "plan_term": null, "plan_term_in_days": null, "plan_key": null, "plan_gross_cost": null, "trialing": false, "credits": { "subscription": null, "non_expiring_tokens": { "balance": 30000, "total_granted": 45000, "total_consumed": 15000, "purchases": [ { "source": "free_grant", "characters": 45000, "price_usd": 0 } ] } } }

POST /revenuecat/webhook

Webhook endpoint for RevenueCat to notify about IAP purchases and refunds.

Headers:

  • Authorization: {REVENUECAT_WEBHOOK_AUTH} - Must match environment variable

Handled Events:

  • NON_RENEWING_PURCHASE - Grants credits for credit pack purchases
  • REFUND - Deducts credits for refunded purchases (negative grant)

Environment Handling:

  • Webhook processes ALL events from RevenueCat (both sandbox and production)
  • Each event's environment (SANDBOX/PRODUCTION) is stored with the grant/event
  • Credits are filtered at query time based on the request's environment parameter

Response:

{ "success": true }

GET /api/credits/balance

Get current credit balance.

Headers:

  • Authorization: Bearer {customer_id}
  • X-Environment: SANDBOX (optional, defaults to PRODUCTION)

Query Parameters:

  • environment=sandbox (optional, alternative to header)

Response:

{ "customer_id": "customer_123", "balance": 45000, "total_granted": 45000, "total_consumed": 0 }

GET /health

Health check endpoint.

Response:

{ "status": "ok", "timestamp": "2024-01-01T00:00:00.000Z" }

Environment Variables

  • ADMIN_ACCESS_KEY - Admin authentication key
  • REVENUECAT_API_KEY - RevenueCat API key for subscription verification
  • REVENUECAT_WEBHOOK_AUTH - Static auth key for webhook endpoint
  • LEMONFOX_API_KEY - LemonFox API key for speech synthesis
  • PDFVECTOR_API_KEY - PDFVector API key for PDF extraction
  • PDF_PARSER_API_KEY - Admin API key for the primary OCR PDF endpoint

Rate Limiting

Subscription Plans

  • Plus: 900,000 characters per calendar month
  • Pro: 2,700,000 characters per calendar month

Non-Expiring Credits

  • Initial grant: 45,000 characters (free for all users)
  • Purchasable packs: 25k, 100k, 500k characters
  • Never expire, used after subscription credits

Special Cases

  • Admin users have unlimited usage
  • Requests exceeding available credits are rejected with HTTP 429
  • Usage is recorded after successful requests only

Error Responses

  • 400 - Bad request (no input text provided)
  • 401 - Missing or invalid authorization
  • 403 - No active subscription or subscription verification failed
  • 429 - Character limit exceeded (both subscription and non-expiring credits exhausted)
  • 500 - Internal server error

Implementation Notes

Idempotency

  • RevenueCat webhook uses transaction IDs to prevent double-crediting
  • Credit grants with the same revenuecat_tx_id are ignored (INSERT OR IGNORE)

Migration Strategy

  • New tables created without modifying existing customer_usage_v1
  • Initial 45k grant applied on first interaction with new system
  • No historical data migration - clean slate approach

Allocation Logic

  1. Calculate available subscription credits (capped at plan limit)
  2. Calculate available non-expiring credits from ledger
  3. Allocate request: subscription first, then non-expiring
  4. Record usage in customer_usage_v1 for all usage
  5. Record debit in credit_debits_v1 for non-expiring portion only
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.