> ## Documentation Index
> Fetch the complete documentation index at: https://magica.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Search models

> Browse Magica's model catalog grouped by category. Find model IDs to use with schema, pricing, credit estimation, and model execution endpoints.

Returns a catalog of available generation models, grouped by category (`text-to-image`, `image-to-video`, `lipsync`, `text-to-speech`, …). Use the optional `query` parameter to filter to a single category or keyword.

<Note>
  This is the REST equivalent of the MCP `search_tools` tool. Both wrap the same
  `searchTools` helper server-side, so the response shape is identical across
  REST and MCP.
</Note>

## Authorizations

<ParamField header="Authorization" type="string" required>
  Bearer API key. Format: `Bearer gx_your_api_key`.
</ParamField>

## Query parameters

<ParamField query="query" type="string">
  Optional category or keyword filter. Examples: `image` (all image categories),
  `video`, `text-to-speech`, `lipsync`, `transcription`, `music-generation`,
  `sound-effects`, `image-to-video` (animate stills). Omit to receive the full
  catalog.
</ParamField>

## Response

<ResponseField name="modelCatalog" type="object">
  Map keyed by **category** (e.g. `text-to-image`). Each entry holds the list of
  models in that category. Present whenever the query matches at least one
  category — i.e. **not** present on `noMatch` responses.
</ResponseField>

<ResponseField name="categoryRoles" type="object">
  Map keyed by category, value is one of `GENERATE` (creates a new asset from
  text — text-to-image, text-to-video, TTS), `EDIT` (modifies an existing asset
  — image-to-image, voice-changing, upscalers), `TRANSFORM` (converts across
  media types — image-to-video, lipsync, audio-translation), or `TRANSCRIBE` /
  `UNDERSTAND` (transcription, multimodal LLM analysis).
</ResponseField>

<ResponseField name="usage" type="string">
  Long-form usage guidance — primarily written for LLM consumers but useful as a
  reminder for human readers. Documents the typical workflow: `search` →
  `model-schema` → `pricing` → estimate → run.
</ResponseField>

<ResponseField name="hint" type="string">
  Optional suggestion (e.g. "Use a query to filter…"). Present when no `query`
  was supplied.
</ResponseField>

<ResponseField name="noMatch" type="string">
  Present **only** when `query` was supplied but matched no categories. Replaces
  `modelCatalog` in that case.
</ResponseField>

<ResponseField name="categoryRoleHints" type="object">
  Returned alongside `noMatch`. Map of role → list of category keywords you
  could try instead.
</ResponseField>

## Request

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    # All categories
    curl https://api.magica.com/api/v1/models/search \
      -H "Authorization: Bearer $MAGICA_API_KEY"

    # Filtered to text-to-speech models only
    curl 'https://api.magica.com/api/v1/models/search?query=text-to-speech' \
      -H "Authorization: Bearer $MAGICA_API_KEY"
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    const res = await fetch(
      "https://api.magica.com/api/v1/models/search?query=image-to-video",
      { headers: { Authorization: `Bearer ${process.env.MAGICA_API_KEY}` } },
    );
    const payload = await res.json();

    if (payload.noMatch) {
      console.warn(payload.noMatch);
    } else {
      // List every modelId in every category
      const modelIds = Object.values(payload.modelCatalog).flatMap(
        (entries) => entries.map((e) => e.modelId),
      );
      console.log(modelIds);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os, requests

    res = requests.get(
        "https://api.magica.com/api/v1/models/search",
        params={"query": "lipsync"},
        headers={"Authorization": f"Bearer {os.environ['MAGICA_API_KEY']}"},
    )
    payload = res.json()

    if "noMatch" in payload:
        print(payload["noMatch"])
    else:
        for category, models in payload["modelCatalog"].items():
            role = payload["categoryRoles"].get(category)
            print(f"{category} ({role}):", [m["modelId"] for m in models])
    ```
  </Tab>
</Tabs>

## Response example — success

```json theme={null}
{
  "modelCatalog": {
    "text-to-image": [
      { "modelId": "flux-2-max-pro", "name": "FLUX 2 Max Pro" },
      {
        "modelId": "nano-banana-2-text",
        "name": "Nano Banana 2 — Text to Image"
      }
    ],
    "image-to-image": [
      { "modelId": "nano-banana-pro-edit", "name": "Nano Banana Pro — Edit" }
    ]
  },
  "categoryRoles": {
    "text-to-image": "GENERATE",
    "image-to-image": "EDIT"
  },
  "usage": "Categories in `modelCatalog` are labelled in `categoryRoles` with one of: GENERATE / EDIT / TRANSFORM / TRANSCRIBE…"
}
```

## Response example — no match

```json theme={null}
{
  "noMatch": "No models matching \"hologram\". Try category keywords: image, video, audio, text-to-image, image-to-image, image-to-video, video-to-video, lipsync, text-to-speech, music-generation, sound-effects, voice-changing, transcription.",
  "categoryRoleHints": {
    "GENERATE": [
      "text-to-image",
      "text-to-video",
      "text-to-speech",
      "music-generation",
      "sound-effects"
    ],
    "EDIT": ["image-to-image", "video-to-video", "voice-changing", "upscaler"],
    "TRANSFORM": ["image-to-video", "lipsync", "audio-translation"]
  },
  "usage": "Categories in `modelCatalog` are labelled in `categoryRoles` with one of: GENERATE / EDIT / TRANSFORM / TRANSCRIBE…"
}
```

The HTTP status is **200** in both cases — always check for `noMatch` before consuming `modelCatalog`.

## Typical workflow

<Steps>
  <Step title="Search">
    `GET /v1/models/search?query=<category>` — get the list of `modelId`s in that category.
  </Step>

  <Step title="Inspect inputs">
    [`GET /v1/models/{modelId}/schema`](/api-reference/nodes/model-schema) — learn what fields the model expects.
  </Step>

  <Step title="(Optional) Estimate cost">
    [`POST /v1/nodes/estimate-credits`](/api-reference/nodes/estimate-credits) — confirm the run won't blow your budget.
  </Step>

  <Step title="Check balance">
    [`GET /v1/credits/balance`](/api-reference/credits/balance) — confirm the account can cover the estimated cost.
  </Step>

  <Step title="Run">
    [`POST /v1/nodes/{nodeType}/run`](/api-reference/nodes/run) — start the generation.
  </Step>
</Steps>

## Errors

| Status | Reason                     |
| ------ | -------------------------- |
| `401`  | Missing or invalid API key |
| `500`  | Catalog assembly failed    |
