> For the complete documentation index, see [llms.txt](https://docs.projectsim.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.projectsim.ai/getting-started/quickstart.md).

# Quickstart

Create an asset from one image, poll until it finishes, and download the result. Python is the default language throughout these docs; cURL examples are also available on the creation guides.

This uses `POST /v1/assets/standard`, a single-asset creation endpoint. If you need measured real-world scale or many variants at once, see [Choose a creation endpoint](/creating-assets/choosing-an-endpoint.md) first.

{% hint style="info" %}
The API base URL is `https://api.projectsim.ai`. Every request needs a bearer token; see [Authentication](/getting-started/authentication.md).
{% endhint %}

## Before you start

{% hint style="info" %}
**Sign-up is self-serve.** Create an account at [dashboard.projectsim.ai](https://dashboard.projectsim.ai/?utm_source=docs\&utm_medium=hint\&utm_content=quickstart), add a payment method on the **Billing** page to start the free trial (your first 40 credits are free), then create your token on the **API token** page. See [Authentication](/getting-started/authentication.md#getting-a-token) for details.

Need a large dataset (1,000–10,000 assets) built for you? [**Request a dataset →**](https://www.projectsim.ai/access?utm_source=docs\&utm_medium=hint\&utm_content=quickstart)
{% endhint %}

Already have a token? Follow the [authentication instructions](/getting-started/authentication.md#use-your-api-token), then continue below.

Use Python 3.11 or later to run the SDK. Install the generated `projectsim` SDK from the provided SDK folder. If working from the API repository, generate it first with `uv sync --locked` and `uv run python scripts/generate_sdk.py` (the server toolchain requires Python 3.12 or later). Run this from the SDK folder’s parent directory:

```bash
python -m pip install ./sdk
```

Set `PROJECTSIM_TOKEN` to your API token in your environment. Run the Python snippets below in order in the same script or session. Replace the example image URL with a publicly reachable HTTPS URL for your image.

The SDK handles bearer authentication, request serialization, and typed response models. The examples use `sync_detailed()` so they can check the HTTP status before reading `response.parsed`. Documented API errors are returned as `Problem` models; unexpected HTTP statuses raise `UnexpectedStatus`.

## 1. Create the asset

`POST /v1/assets/standard` accepts one image (an HTTPS URL we fetch, or inline base64) or a text description, never both and returns `202` immediately — the asset is not ready yet.

{% tabs %}
{% tab title="Python" %}

```python
import os
import uuid

import httpx
from projectsim import AuthenticatedClient
from projectsim.api.assets import create_asset_standard
from projectsim.models import (
    AssetInputs,
    CreateAssetRequest,
    ImageInput,
)

client = AuthenticatedClient(
    base_url="https://api.projectsim.ai",
    token=os.environ["PROJECTSIM_TOKEN"],
    timeout=httpx.Timeout(30.0),
    raise_on_unexpected_status=True,
)

body = CreateAssetRequest(
    name='office chair',
    inputs=AssetInputs(
        images=[ImageInput(
            url='https://example.com/chair.png',
        )],
    ),
)

response = create_asset_standard.sync_detailed(
    client=client,
    body=body,
    idempotency_key=str(uuid.uuid4()),
)
if response.status_code != 202:
    raise RuntimeError(response.content.decode("utf-8"))
created = response.parsed
if created is None:
    raise RuntimeError("The API returned an empty response")
asset_id = created.id
job_id = created.job_id
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl -X POST "https://api.projectsim.ai/v1/assets/standard" \
  -H "Authorization: Bearer $PROJECTSIM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "office chair",
    "inputs": { "images": [{ "url": "https://example.com/chair.png" }] }
  }'
```

{% endtab %}
{% endtabs %}

The response carries `id` (the asset), `job_id`, `status`, and `effective_params` — the resolved parameters used for this asset.

## 2. Poll the job

`GET /v1/jobs/{job_id}` is the single polling surface. `status` moves `queued → running → complete`, or to `failed`. A job that is still `queued` or `running` when its time budget runs out moves to `failed`, so it cannot poll forever.

```python
import time

from projectsim.api.jobs import get_job

while True:
    response = get_job.sync_detailed(client=client, job_id=job_id)
    if response.status_code != 200:
        raise RuntimeError(response.content.decode("utf-8"))
    job = response.parsed
    if job is None:
        raise RuntimeError("The API returned an empty response")
    if job.status == "complete":
        break
    if job.status in {"failed", "canceled", "partial"}:
        raise RuntimeError(f"Job ended with {job.status}: {job.error}")
    time.sleep(5)
```

## 3. Download

`GET /v1/assets/{asset_id}` is both the record and the download surface: every entry in `files` carries a presigned URL valid for one hour. Don't cache the URLs — call again to mint fresh ones. Files appear only once the asset completes; there are no partial downloads. The SDK retrieves these links; `httpx` (installed with the SDK) streams the files from storage.

```python
from pathlib import Path

from projectsim.api.assets import get_asset

response = get_asset.sync_detailed(client=client, asset_id=asset_id)
if response.status_code != 200:
    raise RuntimeError(response.content.decode("utf-8"))
asset = response.parsed
if asset is None:
    raise RuntimeError("The API returned an empty response")
output_dir = Path("downloads").resolve()

for file in asset.files:
    # Preserve relative paths so models can find their textures.
    destination = (output_dir / file.path).resolve()
    if not destination.is_relative_to(output_dir):
        raise ValueError("Unexpected download path")
    destination.parent.mkdir(parents=True, exist_ok=True)
    # Storage URLs are signed; use a separate client without your API token.
    with httpx.stream("GET", file.url, timeout=60, follow_redirects=True) as download:
        download.raise_for_status()
        with destination.open("wb") as target:
            for chunk in download.iter_bytes(chunk_size=1024 * 1024):
                target.write(chunk)
    print(destination)

client.get_httpx_client().close()
```

## Where to go next

* Not sure which endpoint fits? See [Choose a creation endpoint](/creating-assets/choosing-an-endpoint.md).
* Steer mesh, texture, and simulation-ready physics with [Parameters](/creating-assets/parameters.md).
* Need measured real-world scale? See [Measured asset](/creating-assets/assets-measured.md). Need many variants at once? See [High-fidelity](/creating-assets/high-fidelity-variation-set.md) or [Procedural](/creating-assets/procedural-variation-set.md) variation sets.
* Receive completion and failure notifications with [Webhooks](/guides/webhooks.md).
