Python in Practice: API Crest Chat Completions and Async Image Generation Tutorial

Implement synchronous chat and asynchronous image AI API patterns in Python with production best practices.
Based on API Crest's official documentation, this article demonstrates how to use Python to connect to two significantly different AI API calling patterns: synchronous chat completions via the Anthropic SDK (POST /v1/messages), and asynchronous image generation tasks via HTTP requests — including submitting tasks, obtaining a task_id, polling status until is_final is true, and retrieving results from result_url. It emphasizes injecting keys via environment variables rather than hardcoding, and provides a production checklist covering avoiding blind retries, persisting task IDs, monitoring budget consumption, handling 403/429/5xx responses, and choosing protocols per model.
When Your AI App Needs Two Very Different Request Patterns
When building AI applications, developers often encounter two very different API calling scenarios: one is a text conversation request that returns instantly, and the other is an image generation task that requires asynchronous polling. The former demands low-latency synchronous responses, while the latter involves full lifecycle management of task submission, status querying, and result retrieval.
This article is based on official documentation published by API Crest and demonstrates how to implement these two patterns in Python, along with a rundown of key pitfalls that are easy to hit in production environments. Note that this tutorial is written based on the platform's publicly available API documentation — it is an introductory guide, not a benchmark test or actual execution verification of the service's performance.

Environment Setup and Key Management
Installing Dependencies
Before you begin, you need to create an API key in the API Crest developer console and review the model catalog and pricing pages, since requests consume paid credits.
Installing the Python dependencies is straightforward:
python -m pip install anthropic requests
Key Security Best Practices
Never hardcode your API key in source code. The correct approach is to inject it via an environment variable:
export APICREST_API_KEY="YOUR_API_KEY"
Windows PowerShell users can use $env:APICREST_API_KEY = "YOUR_API_KEY" to set the environment variable for the current session. This habit becomes especially important when you move to production — exposing keys in frontend build artifacts is one of the most common security incidents.
Sending Chat Messages with the Anthropic SDK
API Crest documentation describes an Anthropic-style POST /v1/messages route with a base URL of https://api.vv666.ai, offering claude-haiku-5-5 as a model ID.
The core calling code is as follows:
import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["APICREST_API_KEY"],
base_url="https://api.vv666.ai",
)
response = client.messages.create(
model="claude-haiku-5-5",
max_tokens=256,
messages=[
{"role": "user", "content": "Explain idempotency in two sentences."}
],
)
for block in response.content:
if getattr(block, "type", None) == "text":
print(block.text)
The model ID here is an identifier defined in the vendor's documentation, not a generic model name. Different platforms may name the same underlying model completely differently, so be sure to confirm the currently available models and their rates through the platform catalog.
If you need to bypass the SDK and use raw HTTP requests, the equivalent raw form is: send a request to POST /v1/messages with the API key in the authentication header, and a JSON body containing fields such as model, messages, and max_tokens.
On max_tokens and message structure: max_tokens is the maximum number of tokens the model can generate in a single reply, and it affects both latency and cost — setting it too high slows down responses and increases costs, while setting it too low may result in truncated text. The messages array follows a role-alternating conversation structure, where role is typically user or assistant, and the SDK converts it into the JSON body of the underlying HTTP request. Additionally, an Anthropic-style response is not a single string but an array of content blocks, which may contain text blocks, tool call blocks, and other types simultaneously. That's why the example checks the type via block.type == "text" before reading the text field. This multimodal block structure is one of the main differences between the Anthropic protocol and the OpenAI protocol, and understanding it helps you correctly handle returned results when switching vendors.
Async Image Generation: Submitting Tasks and Polling
Unlike the synchronous pattern of chat requests, media generation APIs use an asynchronous workflow. The entire process is divided into four steps:
- Submit the model and prompt to
POST /v1/media/generate - Get the returned
task_id - Poll
GET /v1/media/status?task_id=...untilis_finalis true - On success, read
result_url; on failure, handle the error state
Below is a minimal example based on the gpt-image-2.5 model and media routes from the documentation:
import os
import time
import requests
BASE = "https://api.vv666.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['APICREST_API_KEY']}"}
created = requests.post(
f"{BASE}/v1/media/generate",
headers=HEADERS,
json={
"model": "gpt-image-2.5",
"prompt": "A tiny robot watering a bonsai tree, soft studio lighting",
"params": {"aspect_ratio": "1:1", "quality": "auto"},
},
timeout=30,
)
created.raise_for_status()
task_id = created.json()["task_id"]
for _ in range(20):
check = requests.get(
f"{BASE}/v1/media/status",
headers=HEADERS,
params={"task_id": task_id},
timeout=30,
)
check.raise_for_status()
job = check.json()
if job.get("is_final"):
if job.get("state") == "success":
print("Result:", job.get("result_url"))
break
raise RuntimeError(f"Generation failed: {job.get('error', job.get('state'))}")
time.sleep(5)
else:
print("Job is still pending; keep the task_id and check later.")
The five-second polling interval is illustrative only and is not a rate limit recommendation from the documentation. In production, you should use retry logic with a backoff strategy, persist the task_id, and avoid endlessly polling a task that has already failed.
Polling and backoff strategy: Polling is a pattern where the client repeatedly queries task status at fixed or increasing intervals. The fixed 5-second interval in the example is simple and intuitive, but it has two problems: when there are many tasks, it generates a large number of ineffective requests, consuming quota and even triggering rate limits; when tasks take a long time, it may also drag out the total wait time. Production environments typically use exponential backoff, where the interval is multiplied by a factor (such as 1.5 or 2) after each failure or incomplete check, with a maximum interval cap. At the same time, you should combine a timeout ceiling with a maximum number of polls to avoid waiting indefinitely. A more mature approach is to switch to webhook callbacks or server-side push (such as SSE), where the platform proactively notifies you when a task completes, fundamentally eliminating polling overhead.
Idempotency and duplicate submissions: Idempotency means that executing the same request multiple times has the same effect as executing it once. For creation-type endpoints, this property is crucial: when a client retries because it didn't receive a response due to a timeout, the server may have already accepted the first request. If the endpoint is not idempotent, the retry will create two tasks, resulting in duplicate charges. A common practice is to include a client-generated idempotency key in the request header, which the server uses to deduplicate and return the same result. In async media generation scenarios, even if the platform supports idempotency keys, it's advisable to persist a local record before submission to ensure you can recover when a task is lost rather than blindly resending.
Production Environment Checklist
Before integrating any AI API into production, the following practices are worth verifying one by one:
- Keep keys on the server side: Never expose keys in public frontend code bundles
- Avoid blindly retrying creation requests: If a task has already been accepted but the first response was lost, resubmitting will incur additional charges
- Log task IDs and final states: Especially critical for long-running tasks like images and videos
- Budget control and consumption monitoring: Prices and billing units vary across different models
- Handle 403/429/5xx responses: Covering permission issues, rate limiting, and transient failures
- Choose the protocol based on the model: Don't assume all model routes share the same request format
The Core Challenges of Async Task Management
From practical development experience, async image generation introduces far more complexity than chat completions. Retrying failed chat requests is relatively simple — you just resend the message. But once an image task is submitted, it enters a state machine that needs tracking: the task may succeed, fail, time out, or even become an orphan task after the client loses the task_id.
A more insidious problem lies in cost control. Image generation billing is typically calculated per image or by resolution, and a single round of prompt tuning can trigger dozens of generations. Without task logs and budget alerts, your bill can climb before you realize it.
Another easily overlooked point is protocol differences. The request schemas for different models on the same platform may not be uniform, and directly reusing chat endpoint authentication headers or field names on media endpoints often leads to hard-to-troubleshoot 400 errors.
For developers currently integrating both chat and image generation capabilities, which part do you find more challenging: making costs predictable, managing async task state, or unifying API interfaces across different vendors?
One-line Summary
Implement both synchronous chat and asynchronous image AI API calling patterns in Python, with production best practices.
Paragraph Summary
Based on API Crest's official documentation, this article demonstrates how to connect to two very different AI API calling patterns in Python: one is calling a synchronous chat completion endpoint (POST /v1/messages) via the Anthropic SDK, and the other is handling async image generation tasks via HTTP requests, including submitting tasks, obtaining a task_id, polling status until is_final is true, and then retrieving the result from result_url. The article emphasizes that keys must be injected via environment variables rather than hardcoded, and provides a production environment checklist covering avoiding blindly retrying creation requests, persisting task IDs, monitoring budget consumption, handling 403/429/5xx responses, and choosing protocols per model. The author points out that the core difficulties of async image generation lie in state machine tracking, cost control, and request schema differences across models, and invites readers to discuss which part is hardest to handle.
Related articles

How Big Can Stars Really Get? The Physical Limits of the Largest Stars
Red supergiants are the largest stars by volume, with radii up to 2,000 times the Sun, engulfing the inner solar system. But there is no precise upper limit on stellar size—models predict up to 15,000 solar radii. The most massive blue stars are far smaller than red supergiants—biggest and heaviest are two different things.

Apple HomeView Smart Display Full Feature Breakdown: Wall-Mount or Desktop, $299
Apple launches HomeView, a $299 smart display with wall-mount and desktop options, 12MP camera for facial recognition, smart home control, music playback, and recipe step-by-step cooking. Full breakdown of watch faces, apps, privacy, and cooking integration.

Where Did the Promised New Studio Go? Craft Computing Answers After Five Years
Craft Computing's host responds to viewer questions: the new studio promised five years ago was shelved as construction costs soared from $30,000 to $75,000, and was replaced by a 168-square-foot garage conversion.