Working with language models from Hong Kong requires some pragmatism.
As of August 30, 2026, Hong Kong does not appear on the official lists of supported locations for the OpenAI API or Claude. It is also absent from Google’s published list of available regions for Google AI Studio and the Gemini API. I know informal ways around these restrictions exist, but I do not use them. I want an officially accessible path, which is why Poe and OpenRouter are the two doors I currently use.
I am currently on a career break after working as an Associate Director of Analytics at WPP Media. These are personal analytics and LLM-engineering projects—not a commercial software product with a corporate cloud budget behind it.
But once a Python script starts making hundreds or thousands of API calls without someone reading every response, the operational problem is real regardless of whether it is a commercial application or a personal experiment.
The title is intentionally blunt. A project can technically run on a points-based API. The real distinction is this:
Interactive exploration can be managed through attention and an account-level points balance. An unattended batch run needs its own explicit budget boundary.
That is why I divide my work into two modes.
Two ways of working with language models
In analytics, my LLM work naturally falls into two different tempos.
1. Interactive thinking
The first tempo is conversational.
I may be:
- designing a taxonomy for categorizing advertising creatives;
- working through assumptions behind an analytical model;
- reviewing the logic of a measurement framework;
- translating a vague business requirement into technical steps; or
- interpreting messy marketing data.
In this mode, I am sitting at the screen.
I read every answer, challenge weak reasoning, add context and refine the question turn by turn. If the model introduces irrelevant information or misunderstands the problem, I notice and redirect it.
For this type of work, I want access to the strongest reasoning I can obtain. Cost still matters, but my attention is the main rate limiter. I am choosing when to submit each prompt and reviewing the result before continuing.
2. Batch execution
The second tempo is operational.
I may have a dataset containing hundreds or thousands of unstructured records. Each record needs to be classified, rewritten or converted into structured attributes for tabular analysis.
In this mode, I am not reading every response in a chat interface. I write a script, define an input and output schema, connect it to a data source and let it run.
That creates a different risk.
An unattended job can spend money much faster than an interactive conversation. For example:
- An unusually long record causes the prompt to expand.
- A timeout handler sends duplicate requests.
- A parsing failure triggers an excessive retry loop.
- A malformed response causes the same record to be processed repeatedly.
- Concurrency multiplies a small mistake across many simultaneous requests.
- A model fallback is more expensive than expected.
When I am funding the work personally, I cannot manage interactive thinking and automated execution in the same way.
I want conversational work to have access to frontier reasoning. For batch processing, I want an enforceable, project-specific spending limit.
Poe as my personal gateway to frontier models
For interactive problem-solving, Poe is where I spend much of my conversational time.
Its interface gives me access to multiple frontier models in one place. At the time of writing, Poe lists models including GPT-5.5, GPT-5.6-Sol, Claude Opus 5 and Claude Fable 5 as official bots. This makes it easy to compare how different models approach the same analytical problem without maintaining separate direct-provider accounts.
For example, I can compare how Claude Opus 5 reasons through an ambiguous analytical question with how GPT-5.6-Sol handles a tightly constrained tabular task.
For qualitative work, prompt development and interactive problem-solving, this removes significant friction.
Poe is not limited to the app
An important correction to an earlier version of my thinking is that Poe is not only a consumer chat interface.
I also use the Poe API for personal projects and exercises from the LLM course I am studying. Poe currently provides:
- an OpenAI-compatible Chat Completions API;
- an OpenAI-compatible Responses API;
- an Anthropic-compatible Messages API for Claude models; and
- its native Python library.
Poe’s documentation says its OpenAI-compatible API and native Python library can access public bots on the platform. Exact model availability and supported parameters can change, so I would still check the model list or the bot’s API page before building a job around a particular model.
The practical distinction between Poe and OpenRouter is therefore not that one supports automation and the other does not.
The difference for my workflow is budget granularity and operational control.
Where Poe’s points work well
Poe uses compute points. Depending on the subscription, points may be allocated daily or monthly, and users can inspect their remaining balance and points history. API activity also consumes points from the same account.
For interactive work, this is straightforward:
- I submit a question.
- I review the answer.
- I see the points consumed.
- I decide whether the next interaction is worthwhile.
That is sufficient for human-paced exploration.
The point balance also creates an account-level boundary: Poe documents an insufficient_credits error when the balance is exhausted. But this is different from assigning a separate dollar budget to one specific batch experiment.
Poe’s current public documentation emphasizes account balance and points-history monitoring. I have not found a documented equivalent to OpenRouter’s per-key US-dollar spending limit that would let me say:
This particular script can spend no more than $20, regardless of what else happens on the account.
For many personal scripts, that may not matter. I can still monitor the points balance, estimate usage and stop the job in my own code.
But when I want a project-specific financial boundary that is easy to interpret in dollars, I use OpenRouter.
OpenRouter for budgeted batch runs
I do not use OpenRouter because I am building a commercial software product.
I use it because automated analytics work benefits from controls that are familiar in more formal production environments—even when the project is personal.
OpenRouter uses prepaid credits denominated in US dollars. It also supports spending limits for individual API keys and broader guardrails with daily, weekly or monthly budgets. Requests are rejected when the applicable limit is reached.
That allows me to separate projects operationally.
For example, I can:
- Create a dedicated key for one experiment.
- Assign that key a specific spending limit.
- Disable automatic top-ups if I want the account balance to act as an additional stop.
- Run the script against that key.
- Prevent future requests once the applicable limit is reached.
This is more useful than relying only on a shared account balance.
A practical caveat about “hard” limits
I would not describe any cloud budget as mathematically impossible to exceed under every condition.
OpenRouter notes that budget checks happen before requests are routed, but requests already in flight may complete. This means actual spending can slightly exceed a configured workspace budget, particularly when many calls are running concurrently.
For a tighter practical ceiling, I would therefore:
- use a dedicated API key;
- set a key-level limit below the absolute maximum budget;
- disable auto-top-up;
- control concurrency;
- limit input and output tokens;
- cap the number of retries; and
- leave a small safety margin for in-flight requests.
The control is still much stronger than watching an unattended job draw down a shared balance without a project-specific limit.
Real money on a real batch run
I tested this approach while processing an Amazon product-listing dataset.
The task involved:
- cleaning and reformatting product listings;
- extracting structured technical attributes;
- standardizing naming conventions;
- rewriting selected content; and
- generating metadata for tabular analysis.
A Python script sent records to the API, parsed the returned fields and validated the outputs.
I spent approximately $200 on the larger OpenRouter run.
As the job progressed, the prepaid balance declined. In my run, new requests stopped when the available balance was exhausted. The script did not continue consuming an undefined postpaid budget.
Before the larger batch, I also ran a separately labelled pilot of 1,000 Amazon listings using MiniMax M3. That pilot cost approximately $0.22 and allowed me to test formatting before committing more money.
The lesson was not simply that one model was inexpensive.
It was that batch work should move through stages:
Small sample → output validation → cost estimation → controlled full run
The prepaid balance and key-level spending limit support that discipline.
Regional access still matters
OpenRouter is an aggregator, not a way to ignore an upstream provider’s regional rules.
In my own testing from a Hong Kong IP, OpenRouter did not provide a route to the first-party Google, OpenAI or Anthropic endpoints that were unavailable in my location.
OpenRouter can offer multiple endpoints for a model—including first-party services and cloud-hosted alternatives—and its routing behaviour depends on provider availability, account settings, privacy requirements and regional restrictions.
The broader point is that consumer, developer and third-party access are separate questions.
Even if a consumer Gemini experience becomes accessible in Hong Kong, that does not automatically mean Google AI Studio, the Gemini API or every Google-backed route on an aggregator becomes available. Each product has its own regional and contractual conditions. Google’s current developer-region list still does not include Hong Kong.
My objective is not to bypass those conditions. It is to use officially available third-party services within the access they provide.
Wiring both platforms in Python
Both Poe and OpenRouter expose OpenAI-compatible interfaces, so I can use the official openai Python package and change the client configuration.
import os
from openai import OpenAI
# OpenRouter: for runs that need project-specific spending controls
openrouter_client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
# Poe: for personal projects using the Poe points balance
poe_client = OpenAI(
base_url="https://api.poe.com/v1",
api_key=os.environ["POE_API_KEY"],
)The basic request pattern is similar:
response = poe_client.chat.completions.create(
model="MODEL_NAME",
messages=[
{
"role": "user",
"content": "Classify this marketing record."
}
],
)
print(response.choices[0].message.content)I have also tested LiteLLM’s standard completion() interface against Poe successfully for basic completion tasks.
At the level of sending a prompt and receiving text, the platforms feel similar.
The more important differences appear when I need budget controls, provider routing and structured output.
Where the code diverges: structured output
Free-form prose is often unsuitable for analytics automation.
If I am extracting product attributes, campaign classifications or other tabular fields, I need output that can be validated against a defined schema.
For example:
{
"brand": "Example",
"product_type": "Wireless headphones",
"colour": "Black",
"connectivity": "Bluetooth",
"confidence": 0.94
}A response that merely looks like JSON is not sufficient. The script needs predictable field names, data types and required properties.
The correct implementation differs by platform and endpoint.
1. OpenRouter Chat Completions
OpenRouter’s Chat Completions interface supports structured output through response_format for compatible models.
If I need JSON Schema support, I provide the schema and set provider.require_parameters to true. This tells OpenRouter not to route the request to an endpoint that does not support the parameters I supplied.
Using the OpenAI Python client, that can look like this:
schema = {
"type": "object",
"properties": {
"brand": {"type": "string"},
"product_type": {"type": "string"},
"colour": {"type": ["string", "null"]},
"connectivity": {"type": ["string", "null"]},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
}
},
"required": [
"brand",
"product_type",
"colour",
"connectivity",
"confidence"
],
"additionalProperties": False
}
response = openrouter_client.chat.completions.create(
model="PROVIDER/MODEL_NAME",
messages=[
{
"role": "user",
"content": raw_listing
}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "product_listing",
"strict": True,
"schema": schema
}
},
extra_body={
"provider": {
"require_parameters": True
}
}
)require_parameters is a routing control. It ensures the request is only sent to endpoints that declare support for the requested parameters.
It does not remove the need to validate the returned content in my own application.
2. Responses API on Poe and OpenRouter
Both platforms offer a /responses interface.
On the Responses API, structured output is defined through text.format, not the Chat Completions response_format payload. Poe explicitly documents support for JSON Schema through this pattern, while OpenRouter’s Responses interface also exposes text-format configuration.
For Poe, the request can look like this:
response = poe_client.responses.create(
model="MODEL_NAME",
input=f"Extract the product attributes from this listing:\n\n{raw_listing}",
text={
"format": {
"type": "json_schema",
"name": "product_listing",
"schema": schema
}
}
)
print(response.output_text)If schema-constrained output is central to a Poe workflow, I would prefer its Responses API over trying to force structured output through Chat Completions.
3. Poe Chat Completions
Poe’s Chat Completions endpoint currently ignores the response_format parameter. Poe also notes that unsupported fields may be silently ignored rather than rejected.
If I need to remain on Chat Completions, I can place the schema directly in the prompt:
import json
schema_str = json.dumps(schema)
prompt = f"""
Extract the product data from the input below.
Return only JSON matching this schema:
{schema_str}
Input:
{raw_listing}
"""
response = poe_client.chat.completions.create(
model="MODEL_NAME",
messages=[
{
"role": "user",
"content": prompt
}
]
)I would then parse and validate the output defensively:
from pydantic import ValidationError
raw_output = response.choices[0].message.content
try:
parsed = json.loads(raw_output)
validated = ProductListing.model_validate(parsed)
except (json.JSONDecodeError, ValidationError) as error:
# Log the failure, quarantine the row,
# or retry within a strictly limited policy.
print(error)This is prompt-guided JSON rather than API-enforced structured output, so it requires stronger defensive handling.
The key rule is:
Do not assume that identical-looking parameters behave identically across OpenAI-compatible platforms.
Compatibility makes migration easier, but it does not guarantee feature parity.
Budget controls are only one part of safe batch execution
A spending limit is valuable, but it should be the final circuit breaker rather than the entire operating design.
For personal batch projects, I now think in terms of several layers of protection.
1. Test on a representative sample
A sample should include normal, long, incomplete and malformed records—not only the easiest rows.
2. Estimate cost before scaling
I want to understand approximate input tokens, output tokens and cost per successfully processed record before launching the full dataset.
3. Use a dedicated key
A project-specific API key makes the usage easier to isolate, monitor and stop.
4. Limit retries
Retries should use exponential backoff and have a strict maximum. A permanent validation error should not trigger an infinite loop.
5. Control concurrency
Higher concurrency improves speed but can increase cost exposure before a stop condition is detected.
6. Validate every response
Schema validation should happen before the output is accepted into the final table.
7. Checkpoint progress
The script should save completed records so that a stopped run can resume without paying to process the same rows again.
8. Set token limits
Input and output limits reduce the chance that one unusual record consumes a disproportionate share of the budget.
9. Review data-handling policies
Aggregator routing may involve different underlying providers with different retention policies. OpenRouter provides controls for provider selection, data-collection preferences and zero-data-retention routing, but these settings still need to be configured according to the sensitivity of the project.
For my personal experiments, I avoid sending confidential client or employer data. A commercially sensitive or regulated dataset would require a different level of legal, privacy and security review.
Choosing the right door
Living in Hong Kong has made me more deliberate about how I choose AI tools.
My current division is simple.
I use the Poe app when:
- I am thinking interactively.
- I want to compare frontier models.
- I am framing an ambiguous analytical problem.
- I will read and challenge every response.
- My attention is controlling the rate of usage.
I use the Poe API when:
- I am building a personal or course project.
- The required public model is available through Poe.
- Account-level compute points are an acceptable control.
- I do not require a separate US-dollar budget for that specific run.
- The endpoint supports the features the project needs.
I use OpenRouter when:
- I am running an unattended batch job.
- I want a dedicated API key with a US-dollar spending limit.
- I need control over provider routing.
- I want to require support for parameters such as structured output.
- I need transparent token pricing and project-level cost tracking.
- The required model and provider route are officially available from my location.
Neither platform is universally better.
They solve different operational problems for me.
Final thought
Language-model access is often discussed as a model-selection problem:
- Which model reasons best?
- Which model is fastest?
- Which model has the largest context window?
- Which model is cheapest per million tokens?
Those questions matter, but they are incomplete.
The right model also needs the right operating controls.
For interactive work, my own attention provides much of that control. I submit each prompt intentionally and review each result.
For batch work, attention is no longer enough. The script needs boundaries:
- a defined sample;
- a cost estimate;
- a dedicated key;
- a spending limit;
- bounded retries;
- controlled concurrency;
- structured validation;
- checkpointing; and
- a clear stop condition.
Poe gives me a practical, officially accessible environment for interactive frontier-model work and points-funded personal API projects.
OpenRouter gives me more granular financial and routing controls when a batch project needs an explicit budget.
That is the real distinction.
A conversation can be governed by attention and points. An automated project needs controls that remain in place when nobody is watching.


