Skip to main content
EXPERIMENTAL — This API is in beta. We’re evaluating demand to determine scaling priorities. Your feedback is invaluable — please share your experience on Discord or via email.
Last updated: January 31, 2026

Base URL

https://amara-meshgen-api.01c.ai/meshgen-api

Authentication

All requests require a Bearer token:
Authorization: Bearer sk_amara_YOUR_API_KEY
Get your API key from the Amara web app: ProfileAdmin/Developer PanelDeveloper API.

Endpoints

POST {base_url}                                    # Create generation
GET  {base_url}?id={generation_id}                 # Check status
GET  {base_url}?id={generation_id}&download=true   # Download GLB

Create Generation

Start a new mesh generation from an image. Request Body:
FieldTypeRequiredDescription
imagestringYesBase64-encoded image (PNG, JPEG, or WebP)
modestringNo”fast”, “standard”, or “detailed” (default: “fast”)
promptstringNoText prompt to guide mesh generation
seednumberNoSeed for reproducible results (default: 42)
decimation_targetnumberNoTarget triangle count, 100000-500000 (default: 300000)
texture_sizenumberNoTexture resolution in pixels, 1024-4096 (default: 2048)
Response:
{
  "generation_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "processing",
  "message": "Generation started. Poll the status endpoint for updates.",
  "credits_used": 1,
  "credits_remaining": 99,
  "key_monthly_used": 45,
  "key_monthly_limit": 100
}
Use generation_id for all subsequent API calls (status polling and downloads).
key_monthly_used and key_monthly_limit only appear if your API key has a monthly limit configured.

Check Status

Poll the status of a generation. Response (Queued):
{
  "generation_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "queue_position": 5,
  "total_in_queue": 8
}
Response (Processing):
{
  "generation_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "processing",
  "progress": 45,
  "current_step": "Generating mesh",
  "queue_position": 1,
  "total_in_queue": 3
}
Response (Completed):
{
  "generation_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "glb_url": "https://...",
  "thumbnail_url": "https://...",
  "asset_name": "generated_asset_001",
  "expires_in": 3600
}
expires_in is the number of seconds until glb_url expires. Download and save to your own storage immediately. queue_position indicates your place in line (1 means actively processing).
Response (Failed):
{
  "generation_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "failed",
  "error": "Generation failed",
  "credits_refunded": true
}

Download GLB

Download the GLB file directly. Response: Binary GLB file with headers:
  • Content-Type: model/gltf-binary
  • Content-Disposition: attachment; filename="asset_name.glb"
CRITICAL: You MUST download and save the GLB file to your own storage immediately after generation completes. Files are NOT retained on our servers.

Code Examples

import base64
import time
import requests

API_KEY = "sk_amara_YOUR_API_KEY"
BASE_URL = "https://amara-meshgen-api.01c.ai/meshgen-api"

def generate_mesh(image_path: str, mode: str = "fast") -> str:
    """Generate a 3D mesh from an image. Returns the generation ID."""
    with open(image_path, "rb") as f:
        image_base64 = base64.b64encode(f.read()).decode("utf-8")

    response = requests.post(
        BASE_URL,
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json={"image": image_base64, "mode": mode},
        timeout=60,
    )
    response.raise_for_status()
    return response.json()["generation_id"]

def poll_status(generation_id: str, timeout: int = 600) -> dict:
    """Poll until generation completes or fails."""
    start = time.time()
    while time.time() - start < timeout:
        response = requests.get(
            f"{BASE_URL}?id={generation_id}",
            headers={"Authorization": f"Bearer {API_KEY}"},
        )
        result = response.json()
        if result["status"] in ("completed", "failed"):
            return result
        time.sleep(5)
    raise TimeoutError("Generation timed out")

def download_glb(generation_id: str, output_path: str):
    """Download the GLB file for a completed generation."""
    response = requests.get(
        f"{BASE_URL}?id={generation_id}&download=true",
        headers={"Authorization": f"Bearer {API_KEY}"},
        stream=True,
    )
    response.raise_for_status()
    with open(output_path, "wb") as f:
        for chunk in response.iter_content(chunk_size=8192):
            f.write(chunk)

# Usage
gen_id = generate_mesh("input.png", mode="fast")
result = poll_status(gen_id)
if result["status"] == "completed":
    download_glb(gen_id, "output.glb")

Best Practices

  1. Download & Store Immediately — You MUST download and save GLB files to your own storage immediately. Files are NOT retained on our servers.
  2. Poll Every 5 Seconds — Check status every 5 seconds. Handle queue_position in the response to show users their place in line.
  3. Reproducible Results — Use the same seed value to regenerate identical meshes from the same input image.
  4. Resize Images Client-Side — Resize to max 1024x1024 before encoding to reduce payload size and improve performance.
  5. Exponential Backoff — For production, implement exponential backoff with jitter when polling and retrying.
  6. Handle Rate Limits — Check for retry_after_seconds in 429 responses and wait accordingly.
  7. Store Generation IDs — Persist generation IDs so you can recover from crashes and resume polling later.
  8. Tune Output Quality — Adjust decimation_target (triangle count) and texture_size to balance quality vs file size.

Generation Modes

ModeResolutionCreditsProcessing TimeBest For
fast512px1~30s-2 minPreviews, prototyping
standard1024px1~2-4 minProduction assets
detailed1536px1~4-8 minHigh-quality final assets

Image Requirements

ConstraintLimitNotes
Max payload size3MBBase64-encoded; raw files up to ~2.25MB will fit
Max dimension1024pxLongest side; resize larger images
FormatsPNG, JPEG, WebPData URLs or raw base64 accepted

Rate Limits

LimitValueScope
Generation requests5 per 15 minutesPer API key
Monthly creditsConfigurablePer API key (optional)
Only generation creation (POST) counts against rate limits. Status polling (GET) and downloads are NOT rate-limited, so you can poll as frequently as needed.

Error Codes

HTTP Status Codes

CodeMeaningAction
200SuccessProcess the response
400Bad requestCheck image format/size
401UnauthorizedVerify API key
402Payment requiredAdd credits or check key limits
403ForbiddenKey doesn’t have meshgen scope
429Rate limitedWait and retry
503Service unavailableBackend busy; credits refunded

Error Response Codes

CodeHTTPDescription
IMAGE_VALIDATION_ERROR400Image too large, invalid format, or corrupt
BILLING_ERROR402Organization has insufficient credits
KEY_LIMIT_EXCEEDED402API key’s monthly limit reached
CREDIT_ERROR402Failed to deduct credits
RATE_LIMIT_EXCEEDED429Too many requests
BACKEND_TIMEOUT503Generation service timed out
BACKEND_ERROR503Generation service unavailable
On 503 errors, credits are automatically refunded. Check for credits_refunded: true in the response.

Example Error Responses

400 - Image Validation Error:
{
  "error": "Image too large: 5.2MB. Maximum size: 3MB.",
  "code": "IMAGE_VALIDATION_ERROR",
  "limits": {
    "max_size_mb": 3,
    "supported_formats": ["PNG", "JPEG", "WebP"]
  }
}
429 - Rate Limit Exceeded:
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED",
  "rate_limit": {
    "limit": 5,
    "used": 5,
    "remaining": 0,
    "retry_after_seconds": 847
  }
}
402 - Insufficient Credits:
{
  "error": "Insufficient credits. Required: 1, Available: 0",
  "code": "BILLING_ERROR",
  "credits_required": 1,
  "credits_remaining": 0
}

Support