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

# Diagram images

> Fetch a rendered PNG or SVG of any saved diagram on demand, choose a theme, and embed it anywhere.

Every diagram you store in Just Flow It can be rendered to an image. There are two ways to get one:

<CardGroup cols={2}>
  <Card title="On-demand image endpoint" icon="image" href="#get-an-image-on-demand">
    `GET /v1/diagrams/{id}/image` — render any saved diagram to PNG or SVG, in light or dark theme, at any time.
  </Card>

  <Card title="image_url from a generation" icon="wand-magic-sparkles" href="#image_url-from-a-generation">
    When a `POST /v1/diagrams/generate` job reaches `status: "succeeded"`, the resulting `Generation` carries an `image_url` rendered with the theme/format you requested.
  </Card>
</CardGroup>

## When to use which

<CardGroup cols={2}>
  <Card title="Use the image endpoint when…">
    You already have a diagram `id` and want its picture — for example to display an existing diagram, re-theme it (light ↔ dark), or switch format (PNG ↔ SVG). It does **not** call the AI model and does **not** count against your generation quota.
  </Card>

  <Card title="Use image_url when…">
    You just generated a brand-new diagram and want the rendered image that matches the `theme`/`format` you asked for, without a second round trip. It is only present once the generation has `status: "succeeded"`.
  </Card>
</CardGroup>

<Tip>
  Both paths render the **same saved diagram**. If you need a different theme or format than the one a generation produced, call the image endpoint on the new diagram's `id` — no need to regenerate.
</Tip>

## Get an image on demand

```
GET /v1/diagrams/{id}/image
```

Renders the saved diagram to an image on demand. Requires the `diagrams:read` scope.

<ParamField path="id" type="string" required>
  The UUID of the diagram to render.
</ParamField>

<ParamField query="theme" type="string" default="light">
  Color theme of the rendered image. One of `light` or `dark`.
</ParamField>

<ParamField query="format" type="string" default="png">
  Image format. One of `png` or `svg`.
</ParamField>

### Request

<CodeGroup>
  ```bash curl theme={null}
  curl -L "https://justflow.it/api/v1/diagrams/d290f1ee-6c54-4b01-90e6-d701748f0851/image?theme=dark&format=png" \
    -H "Authorization: Bearer jfi_sk_live_..." \
    -o diagram.png
  ```

  ```js JavaScript (fetch) theme={null}
  const res = await fetch(
    "https://justflow.it/api/v1/diagrams/d290f1ee-6c54-4b01-90e6-d701748f0851/image?theme=dark&format=png",
    { headers: { Authorization: "Bearer jfi_sk_live_..." } }
  );

  if (!res.ok) {
    throw new Error(`Image request failed: ${res.status}`);
  }

  const buffer = Buffer.from(await res.arrayBuffer());
  // e.g. fs.writeFileSync("diagram.png", buffer)
  ```

  ```python Python (requests) theme={null}
  import requests

  res = requests.get(
      "https://justflow.it/api/v1/diagrams/d290f1ee-6c54-4b01-90e6-d701748f0851/image",
      params={"theme": "dark", "format": "png"},
      headers={"Authorization": "Bearer jfi_sk_live_..."},
  )
  res.raise_for_status()

  with open("diagram.png", "wb") as f:
      f.write(res.content)
  ```
</CodeGroup>

### Response

<ResponseField name="200 OK" type="image">
  The rendered diagram image (or a link to it), in the requested `theme` and `format`. The response carries the standard `X-Request-Id` and `RateLimit-*` headers.
</ResponseField>

<Note>
  The image endpoint never calls the AI model and does **not** consume your monthly generation quota. It is still subject to the per-key burst limit (120 requests / 60s).
</Note>

### Errors

<AccordionGroup>
  <Accordion title="404 — unknown diagram">
    A diagram `id` that doesn't exist (or isn't visible to your key) returns `not_found_error` / `resource_not_found`.

    ```json theme={null}
    {
      "error": {
        "type": "not_found_error",
        "code": "resource_not_found",
        "message": "Diagram not found."
      },
      "request_id": "req_8f2c1a9b"
    }
    ```
  </Accordion>

  <Accordion title="400 — invalid theme or format">
    An unsupported `theme` or `format` value returns `invalid_request_error` / `invalid_parameter`, with `param` naming the offending query parameter.

    ```json theme={null}
    {
      "error": {
        "type": "invalid_request_error",
        "code": "invalid_parameter",
        "message": "format must be one of: png, svg.",
        "param": "format"
      },
      "request_id": "req_3d77e0a4"
    }
    ```
  </Accordion>

  <Accordion title="403 — missing scope">
    A key without the `diagrams:read` scope returns `permission_error` / `insufficient_scope`.

    ```json theme={null}
    {
      "error": {
        "type": "permission_error",
        "code": "insufficient_scope",
        "message": "This key is missing the required scope: diagrams:read."
      },
      "request_id": "req_a1b2c3d4"
    }
    ```
  </Accordion>
</AccordionGroup>

## `image_url` from a generation

When you create a diagram from natural language with `POST /v1/diagrams/generate`, the job runs asynchronously. You poll `GET /v1/generations/{id}` until it finishes. On success, the `Generation` object includes both the saved `diagram` and an `image_url` rendered with the `theme` and `format` you requested at generation time.

<Steps>
  <Step title="Start the generation">
    `POST /v1/diagrams/generate` with your `prompt` (and optional `theme`, `format`, `name`, `folder_id`) returns `202 Accepted` with `status: "queued"` and a `Location` header pointing at the generation.
  </Step>

  <Step title="Poll until succeeded">
    `GET /v1/generations/{id}` until `status` is `succeeded` (or `failed`). Honor the `Retry-After` hint between polls.
  </Step>

  <Step title="Read image_url">
    On success, `image_url` is populated alongside the created `diagram`.
  </Step>
</Steps>

A succeeded generation looks like this:

```json theme={null}
{
  "object": "generation",
  "id": "gen_5f9a2b3c4d",
  "status": "succeeded",
  "diagram": {
    "object": "diagram",
    "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
    "name": "Customer onboarding",
    "folder_id": null,
    "organization_id": null,
    "created_by": "a1b2c3d4-0000-0000-0000-000000000000",
    "created_at": "2026-06-07T12:00:05Z",
    "updated_at": "2026-06-07T12:00:05Z"
  },
  "image_url": "/api/v1/diagrams/d290f1ee-6c54-4b01-90e6-d701748f0851/image?theme=light&format=png",
  "error": null,
  "created_at": "2026-06-07T12:00:00Z"
}
```

<ResponseField name="image_url" type="string | null">
  The rendered image for the generated diagram, in the `theme`/`format` requested on the generate call. `null` until the job reaches `status: "succeeded"`. It is a **path relative to the API base** (`https://justflow.it`) — prepend the base to fetch it, e.g. `https://justflow.it{image_url}`.
</ResponseField>

<Warning>
  Don't read `image_url` (or `diagram`) before the job succeeds — both are `null` while `status` is `queued` or `processing`, and on `failed` you'll instead find details in `error`.
</Warning>

## Embedding the image

Because API keys are **server-side only** (no CORS, never exposed in a browser), don't point an `<img src>` directly at the endpoint with a key attached. Instead, fetch the bytes on your server and serve or store them yourself.

<CodeGroup>
  ```js JavaScript (fetch + save) theme={null}
  import { writeFile } from "node:fs/promises";

  async function fetchDiagramImage(id, { theme = "light", format = "png" } = {}) {
    const url = new URL(`https://justflow.it/api/v1/diagrams/${id}/image`);
    url.searchParams.set("theme", theme);
    url.searchParams.set("format", format);

    const res = await fetch(url, {
      headers: { Authorization: "Bearer jfi_sk_live_..." },
    });
    if (!res.ok) throw new Error(`Image request failed: ${res.status}`);

    return Buffer.from(await res.arrayBuffer());
  }

  const png = await fetchDiagramImage("d290f1ee-6c54-4b01-90e6-d701748f0851", {
    theme: "dark",
  });
  await writeFile("onboarding.png", png);
  // Now serve onboarding.png from your own CDN / static host and embed it:
  //   <img src="/assets/onboarding.png" alt="Customer onboarding" />
  ```

  ```python Python (inline SVG) theme={null}
  import requests

  res = requests.get(
      "https://justflow.it/api/v1/diagrams/d290f1ee-6c54-4b01-90e6-d701748f0851/image",
      params={"theme": "light", "format": "svg"},
      headers={"Authorization": "Bearer jfi_sk_live_..."},
  )
  res.raise_for_status()

  svg_markup = res.text  # inline this into your own HTML, server-side
  ```
</CodeGroup>

<Tip>
  Pick `svg` when you want crisp scaling or to inline the markup into a page; pick `png` when you need a flat raster for email, slides, or thumbnails.
</Tip>
