Route support requests by language
Build a Python support form that detects a message's language and assigns an application-owned queue, with a review path for uncertain results.
Working example
ParseAPISet up ParseAPI
Install the Python dependencies in your application and set PARSEAPI_KEY in your server environment.
This Python web app uses Flask for its form and request handler.
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install Flask==3.1.3 httpx==0.28.1
export PARSEAPI_KEY='YOUR_SECRET_API_KEY'Choose a support queue from a message
Build a support-request form that assigns a queue from the submitted message's language. The Python app displays the decision for that exact message. It uses a review queue when detection is uncertain or your team has no configured queue for the language.
This is the routing step in your application. The complete local app does not send messages or create records in a help-desk service.
1. Define the queues your team has
The queue names belong to your app:
QUEUES = {"en": "English support", "fr": "French support", "es": "Spanish support"}
REVIEW_QUEUE = "Language review"Replace these sample names with your own routing rules. Keep a review path instead of assuming every message belongs to English support.
The form accepts one message of up to 2,000 characters. It preserves the entered text and checks a session-bound CSRF token before making a request.
<label for="message">Support message</label>
<textarea id="message" name="message" dir="auto" maxlength="2000" required
aria-describedby="message-hint message-error"
aria-invalid="{{ 'true' if field_error else 'false' }}">{{ message }}</textarea>On a form POST to /, Flask supplies the submitted field:
message = request.form.get("message", "")2. Detect the submitted message
The Python 1.12 SDK has no public language-detection helper. This app uses httpx directly. Its handler reads key from the server's PARSEAPI_KEY and creates an HTTP client for https://api.parseapi.com:
with httpx.Client(
base_url=os.environ.get("PARSEAPI_BASE_URL", "https://api.parseapi.com"),
timeout=10.0, follow_redirects=False,
) as client:
response = client.post(
"/language/detect", json={"text": message.strip()},
headers={"X-API-Key": key, "X-API-Version": "2.1.0"},
)The complete source uses one HTTP client for this request, with https://api.parseapi.com as its base URL, a ten-second timeout and no automatic retries or followed redirects. The secret key stays on the server. The explicit header selects API 2.1 independently of the team's default.
Only the message goes into the POST body. Language identification supports 32 languages and expects one language per request. Short fragments, mixed-language text and unsupported languages can remain unidentified. It does not translate the message.
3. Read the language result
After checking the HTTP status and response version, read the JSON:
detection = response.json()Use the language code for routing and its name for the person reading the result:
language = detection["language"]
language_name = detection["name"]A successful response can contain null language fields. That is an unidentified message, not a failed request. An HTTP error, timeout or malformed response leaves the routing decision unavailable and keeps the message in the form.
4. Apply your queue rule
Choose from the app's configured queues:
queue = QUEUES.get(language, REVIEW_QUEUE)The complete page displays that queue and the detected language. Unidentified text and an identified language with no configured queue both go to Language review, while keeping those two facts distinguishable in the displayed language label.
The handler passes its decision into the ticket template value:
<p class="queue">{{ ticket.queue }}</p>
<p>Detected language: {{ ticket.language_name }}</p>Use this decision when assigning a ticket in your own application. Retain the original message and let a person correct the assignment. A language label describes this message; it does not establish the author's permanent language preference or nationality.
The complete app does not retain message text in session cookies or log it. Use a stable APP_SECRET when adapting it to your own deployment.
The result is a queue decision, not a claim that a help-desk ticket was saved or delivered. See the Language reference for detection fields and limits.
Complete example
Use the complete implementation as a reference when adding this feature to your app. It includes the validation and failure handling around the calls above.
View and copy the complete source
"""A local support-request form with application-owned language queue rules."""
import hmac
import os
import re
import secrets
import httpx
from flask import Flask, render_template_string, request, session
app = Flask(__name__)
app.secret_key = os.environ.get("APP_SECRET") or secrets.token_hex(32)
app.config.update(MAX_CONTENT_LENGTH=16384, SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax", TRUSTED_HOSTS=["127.0.0.1", "localhost"])
# example:queues
QUEUES = {"en": "English support", "fr": "French support", "es": "Spanish support"}
REVIEW_QUEUE = "Language review"
# /example:queues
PAGE = """<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="data:,"><title>Route a support request</title><style>
:root{color-scheme:light dark;--bg:#f7f8fa;--panel:#fff;--text:#17191d;--muted:#595e68;--line:#d5d9e0;--accent:#2459d3;--error:#a51d2d}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
main{width:min(100% - 40px,640px);margin:72px auto}h1{font-size:clamp(2rem,6vw,2.8rem);line-height:1.12;letter-spacing:-.045em;margin:0 0 18px}h2{font-size:1rem;margin:0 0 12px}p{margin:0 0 20px}.intro,.note{color:var(--muted)}
form,.result{background:var(--panel);border:1px solid var(--line);border-radius:18px;padding:28px;margin-top:28px}label{display:block;font-size:.9rem;font-weight:600;margin-bottom:8px}textarea,button{font:inherit;border-radius:9px}textarea{display:block;width:100%;min-height:170px;resize:vertical;color:var(--text);background:var(--panel);border:1px solid var(--line);padding:12px}
button{min-height:46px;background:var(--accent);color:#fff;border:0;padding:11px 22px;margin-top:22px;font-weight:600;cursor:pointer}button:hover{filter:brightness(.93)}:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.error{color:var(--error);font-size:.9rem;margin:8px 0 0}[aria-invalid=true]{border-color:var(--error)}.alert{padding:16px;border:1px solid var(--error);border-radius:10px;margin-top:24px;color:var(--error)}
.queue{font-size:clamp(1.6rem,5vw,2.2rem);font-weight:650;letter-spacing:-.03em;margin-bottom:8px}.note{font-size:.9rem;margin:18px 0 0}
@media(max-width:440px){main{margin:40px auto}form,.result{padding:22px}}
@media(prefers-color-scheme:dark){:root{--bg:#111316;--panel:#1b1e23;--text:#f3f4f6;--muted:#b3b8c3;--line:#444a55;--accent:#759cff;--error:#ff9ca8}button{color:#111316}}
</style></head><body><main><h1>Route a support request</h1><p class="intro">Find the right support queue from the language of a message.</p>
{% if error %}<p class="alert" role="alert">{{ error }}</p>{% endif %}
<form method="post" action="/"><input type="hidden" name="csrf" value="{{ csrf }}">
<!-- example:form -->
<label for="message">Support message</label>
<textarea id="message" name="message" dir="auto" maxlength="2000" required
aria-describedby="message-hint message-error"
aria-invalid="{{ 'true' if field_error else 'false' }}">{{ message }}</textarea>
<!-- /example:form -->
<p class="note" id="message-hint">Use one language per request, up to 2,000 characters.</p><p class="error" id="message-error">{{ field_error or '' }}</p><button type="submit">Find support queue</button></form>
{% if ticket %}<section class="result" role="status" aria-live="polite"><h2>Route for this request</h2>
<!-- example:result -->
<p class="queue">{{ ticket.queue }}</p>
<p>Detected language: {{ ticket.language_name }}</p>
<!-- /example:result -->
<p class="note">{% if ticket.review %}Keep this request for a person to review. Its language is unidentified or has no configured queue.{% else %}This queue matches the language of the submitted message.{% endif %}</p></section>{% endif %}
<p class="note">This page chooses a queue. It does not send a message or create a help-desk ticket.</p></main></body></html>"""
@app.after_request
def private_response(response):
response.headers["Cache-Control"] = "no-store"
response.headers["X-Content-Type-Options"] = "nosniff"
return response
@app.errorhandler(413)
def too_large(_error):
return "<h1>Request too large</h1><p>Go back and use a message of at most 2,000 characters.</p>", 413
@app.route("/", methods=["GET", "POST"])
def index():
message, field_error, error, ticket, status = "", None, None, None, 200
token = session.setdefault("csrf", secrets.token_urlsafe(32))
if request.method == "POST":
# example:input
message = request.form.get("message", "")
# /example:input
submitted = request.form.get("csrf", "")
if not submitted.isascii() or not hmac.compare_digest(submitted, token):
error, status = "This form expired. Refresh the page and try again.", 403
elif not message.strip() or len(message) > 2000 or "\x00" in message:
field_error, status = "Enter a message of 1 to 2,000 characters.", 422
else:
key = os.environ.get("PARSEAPI_KEY", "").strip()
if not key or key.startswith(("parse_public_", "parse_app_")):
error, status = "The language check is unavailable. Please try again later.", 503
else:
try:
# Explicit direct HTTP: Python1.12 has no language-detection helper.
# example:request
with httpx.Client(
base_url=os.environ.get("PARSEAPI_BASE_URL", "https://api.parseapi.com"),
timeout=10.0, follow_redirects=False,
) as client:
response = client.post(
"/language/detect", json={"text": message.strip()},
headers={"X-API-Key": key, "X-API-Version": "2.1.0"},
)
# /example:request
response.raise_for_status()
if response.headers.get("X-API-Version") != "2.1.0":
raise ValueError("Unexpected API version.")
# example:response
detection = response.json()
# /example:response
if not isinstance(detection, dict) or not all(name in detection for name in ("language", "name")):
raise ValueError("Unexpected detection response.")
if detection["language"] is None:
if detection["name"] is not None:
raise ValueError("Unexpected unidentified response.")
elif (not isinstance(detection["language"], str)
or not re.fullmatch(r"[a-z]{2,3}", detection["language"])
or not isinstance(detection["name"], str) or not detection["name"].strip()):
raise ValueError("Unexpected detection fields.")
# example:fields
language = detection["language"]
language_name = detection["name"]
# /example:fields
# example:route
queue = QUEUES.get(language, REVIEW_QUEUE)
# /example:route
ticket = {"queue": queue, "language_name": language_name or "Not identified",
"review": language not in QUEUES}
except httpx.HTTPStatusError as failure:
if failure.response.status_code == 400:
field_error, status = "This message could not be checked. Review the text and try again.", 422
else:
error, status = "The language check did not complete. Please try again later.", 503
except (httpx.RequestError, ValueError):
error, status = "The language response could not be used. Please try again later.", 503
return render_template_string(PAGE, message=message, field_error=field_error, error=error,
ticket=ticket, csrf=token), status
if __name__ == "__main__":
app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "8000")), debug=False, use_reloader=False)
Try the complete example on your computer
After completing the setup above, save the source in your application folder and start the app:
python3 support-language-routing.python.pyOpen 127.0.0.1:8000 to use the form. The server keeps your secret key out of the page.
For every field and parameter, see Language API reference.