Providers
Included AI providers, available models, and how to configure your own custom providers.
Asset Tap uses a data-driven provider system where AI providers are defined entirely through YAML configuration files. No code changes are needed to add, remove, or modify providers.
Included Providers
Asset Tap ships with pre-configured support for two providers. You only need an API key for one of them to run the full pipeline.
Asset Tap is not tied to either service: the provider layer is a compatibility surface, support for more providers is coming, and anyone can add their own with a YAML file.
Model tables follow one ordering everywhere: model families ascend (older/lower tier first, newest last) and appear in the same order under every provider that carries them; specialized and provider-exclusive models follow their family; each provider's default is marked inline. The tables compare like-for-like at a glance.
- fal.ai -- Pay-per-generation pricing, broadest model selection.
- Meshy AI -- Subscription-based, credit pool; specialized in 3D.
fal.ai
Text-to-Image Models
| Model | Description |
|---|---|
| Nano Banana | Google Imagen 3-based image generation -- fast and affordable |
| Nano Banana 2 | Gemini 3.1 Flash Image -- reasoning-guided generation (default) |
| Nano Banana Pro | Premium Google Imagen 3 -- higher quality with aspect ratio control |
| FLUX.2 Dev | Open-source FLUX.2 with tunable guidance and steps |
| FLUX.2 Pro | Premium FLUX.2 -- best quality, zero-config |
Image-to-3D Models
| Model | Description |
|---|---|
| TRELLIS 2 | Native 3D generative model -- fast and versatile (default) |
| Hunyuan3D Pro | Tencent Hunyuan3D v3.1 Pro -- high quality 3D generation |
| Meshy v6 | Meshy 6 proxied through fal -- pay-per-call billing |
| Meshy v7 | Meshy 7 proxied through fal -- pay-per-call billing |
Tip: Your fal.ai Dashboard shows all generation requests, results, and costs. This is the source of truth for your usage and a handy way to recover past outputs.
Meshy AI
Native Meshy API -- bypasses fal's proxy markup and unlocks the full Meshy feature set. Requires MESHY_API_KEY from the Meshy API settings page.
Text-to-Image Models
| Model | --image-model | Description |
|---|---|---|
| Nano Banana | meshy/nano-banana | Standard tier (default) |
| Nano Banana 2 | meshy/nano-banana-2 | Mid tier |
| Nano Banana Pro | meshy/nano-banana-pro | Higher quality |
| GPT Image 2 | meshy/gpt-image-2 | The only Meshy model offering 2:3 |
Tunable parameters: aspect_ratio, generate_multi_view, pose_mode, remove_background (transparent RGBA PNG output).
Aspect ratios differ per model. The Nano Banana family accepts 1:1, 16:9, 9:16, 4:3, 3:4; GPT Image 2 accepts 1:1, 3:2, 2:3 only. generate_multi_view cannot be combined with aspect_ratio; clear it with --param aspect_ratio= (or the (unset) entry in the GUI dropdown) when enabling multi-view.
Image-to-3D Models
| Model | --3d-model | Description |
|---|---|---|
| Meshy v5 | meshy/v5/image-to-3d | Previous generation |
| Meshy v6 | meshy/v6/image-to-3d | Meshy 6 -- production-ready 3D with PBR textures |
| Meshy v7 | meshy/v7/image-to-3d | Meshy 7 -- newest generation, supports Ultra mode (default) |
| Smart Topology | meshy/t2/image-to-3d | Meshy T2 -- clean topology, separated parts, game-ready face counts (max 15k) |
Tunable parameters (v5/v6/v7): topology (triangle/quad), target_polycount, enable_pbr, should_remesh, should_texture, pose_mode, texture_prompt. Smart Topology sets its face count directly with target_polycount (100-15,000) -- topology and should_remesh don't apply.
Version-specific knobs, per Meshy's own docs: v6 and v7 add texture_resolution (2k/4k/8k) and image_enhancement; remove_lighting is v6-only; ultra_mode (higher-fidelity geometry) is v7-only. symmetry_mode remains on v5/v6 but is deprecated by Meshy and no longer affects output.
Why two ways to reach Meshy? The fal.ai "Meshy v6" entry uses fal's pay-per-call billing and requires a
FAL_KEY. The Meshy provider's entry uses Meshy's subscription credits and requires aMESHY_API_KEY. Pick whichever fits your billing relationship -- or keep both keys configured and switch per generation.
Pricing Models
| Provider | Billing | How it works |
|---|---|---|
| fal.ai | Pay-per-call | Charged per generation at the model's listed cost; no monthly minimum. |
| Meshy AI | Subscription | Monthly plan grants a credit pool; each generation deducts credits from it. |
Per-generation costs are set by the providers and change without notice; check their pricing pages against your own key.
Adding Custom Providers
You can add support for any AI provider by creating a YAML configuration file. No code changes required.
Quick Start
Create a YAML file with your provider's API details:
provider:
id: 'my-provider'
name: 'My Provider'
description: 'Custom AI provider'
env_vars: ['MY_API_KEY']
base_url: 'https://api.example.com'
api_key_url: 'https://example.com/keys'
text_to_image:
- id: 'my-model'
name: 'My Model'
description: 'Fast image generation'
endpoint: '/generate'
method: POST
request:
headers:
Authorization: 'Bearer ${MY_API_KEY}'
Content-Type: 'application/json'
body:
prompt: '${prompt}'
response:
response_type: json
field: 'image_url'Where to Put Your Config
For personal use (no rebuild needed):
Place the YAML file in your user config directory:
- macOS:
~/Library/Application Support/asset-tap/providers/my-provider.yaml - Linux:
~/.config/asset-tap/providers/my-provider.yaml - Windows:
%APPDATA%/asset-tap/providers/my-provider.yaml
Restart the application and your provider will appear automatically.
To embed in the binary (requires rebuild):
- Add the file to
providers/my-provider.yamlin the source tree - Run
make build-- theinclude_dir!macro discovers all*.yamlfiles automatically
Authentication
List required environment variables in env_vars. The provider won't appear as available until all are set.
provider:
env_vars: ['MY_API_KEY', 'MY_SECRET']
Use ${ENV_VAR} syntax in request templates:
request:
headers:
Authorization: 'Bearer ${MY_API_KEY}'
In the GUI, set API keys in Settings. For the CLI, use environment variables or a .env file.
Response Types
JSON -- Extract a URL from a JSON response:
response:
response_type: json
field: 'data.images[0].url' # JSONPath expression
Polling -- For async APIs that queue jobs:
response:
response_type: polling
polling:
status_field: 'status_url' # Field in submit response containing the status check URL
status_check_field: 'status' # Field in status response to check
success_value: 'COMPLETED'
failure_value: 'FAILED'
response_url_field: 'response_url' # Field containing URL to fetch final result
response_envelope_field: 'response' # Field in result that wraps the actual output
result_field: 'images[0].url' # JSONPath to extract from the output
interval_ms: 1000
max_attempts: 120
# Optional: build the poll URL from a task id instead of reading a full URL
# from the initial response. Used when the API returns only {"result": "<id>"}.
status_url_template: '/v1/jobs/${result}'
# Optional: override the cancel HTTP method. Defaults to PUT.
# Meshy uses DELETE for its cancel endpoint.
cancel_method: DELETE
cancel_url_template: '${status_url}'
status_url_template supports nested paths (${data.id}) and array indices (${items[0]}). Relative paths are resolved against base_url.
Binary / Base64 -- For direct file responses:
response:
response_type: binary
# or base64-encoded in JSON:
response:
response_type: base64
field: 'artifacts[0].base64'Image-to-3D Models
Image-to-3D models use ${image_url} instead of ${prompt}. Asset Tap automatically uploads the image and substitutes the public URL:
image_to_3d:
- id: 'my-3d-model'
name: 'My 3D Model'
endpoint: '/3d/generate'
method: POST
request:
headers:
Authorization: 'Key ${MY_API_KEY}'
Content-Type: 'application/json'
body:
image_url: '${image_url}'
response:
response_type: polling
polling:
status_field: 'id'
status_check_field: 'status'
success_value: 'succeeded'
result_field: 'model_glb.url'
interval_ms: 2000
max_attempts: 300Upload Configuration
Required when models use ${image_url} and the provider exposes an upload endpoint. The upload section is nested under provider::
provider:
id: 'my-provider'
# ... other provider fields ...
upload:
endpoint: '/storage/upload/initiate'
method: POST
request:
type: initiate_then_put # or "multipart"
headers:
Authorization: 'Key ${MY_API_KEY}'
Content-Type: 'application/json'
initiate_body:
file_name: 'image.png'
content_type: 'image/png'
response:
upload_url_field: 'upload_url'
file_url_field: 'file_url'Data-URI Fallback (No Upload Endpoint)
If a provider doesn't offer an upload endpoint but accepts inline data:image/png;base64,... URIs directly (like Meshy), simply omit the upload: block from your YAML. Asset Tap automatically inlines the image as a base64 data URI wherever ${image_url} appears in the request body.
A 10 MB cap on the raw image bytes is enforced in this mode to prevent request-size failures on providers with body limits. For typical Asset Tap workflows (where the intermediate image is 1-4 MB), this is well within limits.
Testing Your Provider
# 1. Verify the provider loads and config is valid
asset-tap --list-providers
# 2. Test with the real API (validates response parsing)
asset-tap -p my-provider -y "a red cube"
Working from a source build? The repository's developer docs cover mock mode, which exercises a provider config without spending credits.
Schema Reference
Complete reference for all provider YAML fields.
Top-Level Structure
provider: # Required: Provider metadata
id: string
name: string
description: string
env_vars: [string]
base_url: string # Optional
api_key_url: string # Optional
website_url: string # Optional
docs_url: string # Optional
upload: # Optional: File upload configuration (nested under provider)
text_to_image: # Optional: Text-to-image model list
- id: string
image_to_3d: # Optional: Image-to-3D model list
- id: stringModel Fields
text_to_image: # or image_to_3d
- id: string # Unique model ID within provider
name: string # Display name
description: string # Model description
endpoint: string # API endpoint (relative to base_url or absolute)
method: string # HTTP method (default: POST)
request:
headers: {} # HTTP headers with ${VAR} interpolation
body: {} # JSON body with ${prompt} or ${image_url}
response:
response_type: string # Json, Binary, Base64, or polling
field: string # JSONPath for Json/Base64
polling: # Required for polling type
status_field: string
status_url_template: string # Optional: build poll URL from initial response
status_check_field: string
success_value: string
failure_value: string
result_field: string
interval_ms: integer
max_attempts: integer
cancel_method: string # Optional: HTTP method for cancel (default PUT)
cancel_url_template: string # Optional: template using ${status_url}
parameters: [] # Optional: user-tunable fields (see below)Tunable Parameters
A model can declare parameters that users adjust per generation. They appear in the GUI as sliders, checkboxes, and dropdowns, and on the CLI via --param KEY=VALUE.
parameters:
- name: 'guidance_scale' # Must match a key in request.body
label: 'Guidance Scale' # GUI label
description: 'Higher = stricter prompt adherence'
type: float # float, integer, boolean, string, select
default: 3.5
min: 1.0
max: 20.0
step: 0.5
- name: 'topology'
label: 'Topology'
type: select
default: 'triangle'
options: ['triangle', 'quad'] # Required for select; strings or numbers
- name: 'seed'
label: 'Seed'
type: integer
widget: input # Typed field instead of a slider
default: null # Omitted from the request unless the user sets it| Field | Required | Applies to | Purpose |
|---|---|---|---|
name | yes | all | Must match a request-body key |
label | yes | all | GUI display name |
description | no | all | Tooltip text |
type | yes | all | float, integer, boolean, string, select |
default | yes | all | Used when no override exists |
min / max / step | no | float, integer | Slider bounds and increment |
options | yes for select | select | Allowed values (strings or numbers) |
widget | no | float, integer, string | slider (default) or input |
allow_unset | no | select | Adds an (unset) entry that clears to null |
Null means "unset". A null default, a cleared input widget, or --param name= on the CLI all drop the key from the request so the provider applies its own default. A literal null is never sent.
allow_unset for mutually exclusive parameters. A dropdown can only write one of its options, so set allow_unset: true when a parameter must sometimes be absent, for example when the provider rejects it alongside another parameter.
Variable Interpolation
${prompt}-- User's text prompt${image_url}-- Publicly accessible URL for the generated image. Produced by the provider'suploadendpoint if configured, otherwise inlined as adata:image/png;base64,...URI.${ENV_VAR}-- Any environment variable listed inenv_vars
Complete Example
provider:
id: 'fal.ai'
name: 'fal.ai'
description: 'Fast, serverless AI model API'
env_vars: ['FAL_KEY']
base_url: 'https://queue.fal.run'
api_key_url: 'https://fal.ai/dashboard/keys'
upload:
endpoint: 'https://rest.alpha.fal.ai/storage/upload/initiate?storage_type=fal-cdn-v3'
method: POST
request:
type: initiate_then_put
headers:
Authorization: 'Key ${FAL_KEY}'
Content-Type: 'application/json'
initiate_body:
file_name: 'image.png'
content_type: 'image/png'
response:
upload_url_field: 'upload_url'
file_url_field: 'file_url'
text_to_image:
- id: 'fal-ai/nano-banana-2'
name: 'Nano Banana 2'
description: 'Gemini 3.1 Flash Image -- reasoning-guided generation'
endpoint: '/fal-ai/nano-banana-2'
method: POST
request:
headers:
Authorization: 'Key ${FAL_KEY}'
Content-Type: 'application/json'
body:
prompt: '${prompt}'
resolution: '1K'
num_images: 1
response:
response_type: polling
polling:
status_field: 'status_url'
status_check_field: 'status'
success_value: 'COMPLETED'
failure_value: 'FAILED'
response_url_field: 'response_url'
response_envelope_field: 'response'
result_field: 'images[0].url'
interval_ms: 1000
max_attempts: 120
image_to_3d:
- id: 'fal-ai/trellis-2'
name: 'Trellis 2'
description: 'High quality 3D model generation'
endpoint: '/fal-ai/trellis-2'
method: POST
request:
headers:
Authorization: 'Key ${FAL_KEY}'
Content-Type: 'application/json'
body:
image_url: '${image_url}'
response:
response_type: polling
polling:
status_field: 'status_url'
status_check_field: 'status'
success_value: 'COMPLETED'
failure_value: 'FAILED'
response_url_field: 'response_url'
response_envelope_field: 'response'
result_field: 'model_glb.url'
interval_ms: 2000
max_attempts: 300Best Practices
- Always use HTTPS for all URLs
- Never hardcode API keys -- use
${ENV_VAR}syntax - Verify JSONPath expressions against actual API responses
- Use reasonable polling intervals to respect rate limits
- Set adequate
max_attemptsbased on typical operation time - Verify the config loads (
--list-providers) before testing against the real API to validate response parsing