[KongchangAI]
· 3 min read· 1,533 words

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

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.

API Crest Python tutorial source screenshot

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:

  1. Submit the model and prompt to POST /v1/media/generate
  2. Get the returned task_id
  3. Poll GET /v1/media/status?task_id=... until is_final is true
  4. 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.

#API Crest#Python API tutorial#Anthropic SDK#async image generation#chat completions#task polling#API key management#production best practices
Share:

Related articles