$ a text lingo conversion REST API powered by Gemini
ToneForged is a public REST API that converts text into specific lingos using AI. Send a string and a tone name (Pirate, Medieval, Shakespeare, etc...) and the API returns the text rewritten in that style using Google's Gemini model. The conversion is contextual rather than a word-swap, so the result reads naturally in the target tone.
Rate limits are strict because both Gemini and the EC2 instance are on free tiers. Started as a personal experiment with Spring Boot and the Gemini SDK, made public for anyone to build on top of.
Each tone is implemented as a Strategy: a class that holds the tone-specific prompt template used to
instruct Gemini. All tones register themselves in a static TONE_MAP keyed by name string. When a
/api/convert request arrives, the controller looks up the tone by name from the map,
constructs the prompt, and sends it to Gemini. This means adding a new tone requires only a new
Strategy class and a map entry, no controller changes.
The Gemini response goes through explicit safety checking before being returned: the code inspects
the finish reason and handles SAFETY, RECITATION, MAX_TOKENS,
STOP, and empty-response cases separately, returning an appropriate error field in the
JSON response rather than letting unexpected cases propagate as 500s.
Rate limiting is enforced by a HandlerInterceptor that runs before every request. It
stores a RequestInfo record (count, window start timestamp) per IP address in a
ConcurrentHashMap. On each request, it checks whether the count in the current
60-second window exceeds 2; if so, it returns 429 immediately. The RequestInfo type is
immutable - each update creates a new record rather than mutating in place, which avoids race
conditions on the map update.
Input is capped at 5000 characters. The API is open. No authentication is required.
/api/strategies, returns a list of all available tone names
/api/convert, converts text to the specified toneSend a POST to http://52.207.146.61/api/convert with a JSON body:
{
"strategy": "Pirate",
"text": "Hello, Friend"
}
{
"original": "Hello, Friend",
"converted": "Ahoy Matey!",
"lingo": "Pirate",
"error": null
}
const response = await fetch(
`http://52.207.146.61/api/convert`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ strategy, text })
}
);
Building a deployed public API on a free-tier stack forced practical decisions around rate limiting, error handling, and deployment size. Some key takeaways: