> ## 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.

# Quickstart

> Create an API key, make your first authenticated request, and generate a diagram from a prompt.

Get from zero to a generated diagram in three steps: create a key, list your diagrams, then generate a new diagram from a natural-language prompt and poll for its `image_url`.

<Note>
  The Just Flow It API is **server-to-server only**. There is no CORS support — never embed an API key in a browser or mobile client. All requests go to `https://justflow.it/api/v1`.
</Note>

<Steps>
  <Step title="Create an API key">
    API keys are created in the web app, not via the API.

    1. Open Just Flow It and go to **Settings → API keys**.
    2. Click **Create key** and choose the scopes you need (`diagrams:read`, `diagrams:write`, `folders:read`, `folders:write`, `generate`).
    3. Copy the key immediately — it is shown in **plaintext only once** at creation. Only a SHA-256 hash is stored server-side.

    Keys look like `jfi_sk_live_xxxxx` (production) or `jfi_sk_test_xxxxx` (staging).

    <Warning>
      The API is available on **paid plans only**. A **Pro** user receives a personal key that acts on their personal diagrams and folders; a member of a **Team** organization receives an org key that acts on that org's resources. Free-plan keys are rejected with `403 plan_required`.
    </Warning>

    <Tip>
      Store the key in an environment variable so it never lands in source control:

      ```bash theme={null}
      export JFI_API_KEY="jfi_sk_live_xxxxx"
      ```
    </Tip>
  </Step>

  <Step title="Make your first authenticated request">
    Authenticate with HTTP Bearer auth and list your diagrams. This call requires the `diagrams:read` scope.

    <CodeGroup>
      ```bash curl theme={null}
      curl https://justflow.it/api/v1/diagrams?limit=20 \
        -H "Authorization: Bearer $JFI_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      const res = await fetch("https://justflow.it/api/v1/diagrams?limit=20", {
        headers: {
          Authorization: `Bearer ${process.env.JFI_API_KEY}`,
        },
      });
      const body = await res.json();
      console.log(body.data);
      ```

      ```python Python theme={null}
      import os
      import requests

      res = requests.get(
          "https://justflow.it/api/v1/diagrams",
          params={"limit": 20},
          headers={"Authorization": f"Bearer {os.environ['JFI_API_KEY']}"},
      )
      res.raise_for_status()
      print(res.json()["data"])
      ```
    </CodeGroup>

    A successful response is a `200` with a cursor-paginated list of diagrams:

    ```json theme={null}
    {
      "data": [
        {
          "object": "diagram",
          "id": "9b2d6f1c-3a8e-4c2b-9f10-7a1e5d4c8b00",
          "name": "Onboarding process",
          "folder_id": null,
          "organization_id": null,
          "created_by": "1f0c9a44-6b2d-4e8a-9c31-2d5f7e0a1b22",
          "created_at": "2026-06-01T12:30:00Z",
          "updated_at": "2026-06-01T12:30:00Z"
        }
      ],
      "has_more": false,
      "next_cursor": null
    }
    ```

    To page through results, pass `next_cursor` back as the `cursor` query parameter on your next request.

    <Accordion title="If you get a 401 or 403">
      Every error uses the same envelope, and the response carries an `X-Request-Id` header:

      ```json theme={null}
      {
        "error": {
          "type": "authentication_error",
          "code": "invalid_api_key",
          "message": "The provided API key is invalid."
        },
        "request_id": "req_8f3a9c2e1b"
      }
      ```

      * `401 missing_api_key` / `invalid_api_key` / `revoked_api_key` / `expired_api_key` — check the `Authorization` header. `401` responses also include a `WWW-Authenticate: Bearer` header.
      * `403 plan_required` — your plan does not include API access.
      * `403 insufficient_scope` — the key is missing the `diagrams:read` scope.
    </Accordion>
  </Step>

  <Step title="Generate a diagram from a prompt">
    Diagram generation runs an LLM and a render, so it is **asynchronous**: you create a generation job, then poll it until it succeeds. This call requires the `generate` scope and counts against your monthly generation quota.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://justflow.it/api/v1/diagrams/generate \
        -H "Authorization: Bearer $JFI_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "prompt": "An invoice approval process: receive invoice, validate, route to manager for approval, then pay or reject.",
          "name": "Invoice approval",
          "theme": "light",
          "format": "png"
        }'
      ```

      ```javascript JavaScript theme={null}
      const res = await fetch("https://justflow.it/api/v1/diagrams/generate", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.JFI_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          prompt:
            "An invoice approval process: receive invoice, validate, route to manager for approval, then pay or reject.",
          name: "Invoice approval",
          theme: "light",
          format: "png",
        }),
      });
      const generation = await res.json();
      console.log(generation.id, generation.status); // gen_..., "queued"
      ```

      ```python Python theme={null}
      import os
      import requests

      res = requests.post(
          "https://justflow.it/api/v1/diagrams/generate",
          headers={"Authorization": f"Bearer {os.environ['JFI_API_KEY']}"},
          json={
              "prompt": "An invoice approval process: receive invoice, validate, "
              "route to manager for approval, then pay or reject.",
              "name": "Invoice approval",
              "theme": "light",
              "format": "png",
          },
      )
      res.raise_for_status()
      generation = res.json()
      print(generation["id"], generation["status"])  # gen_..., "queued"
      ```
    </CodeGroup>

    The request returns `202 Accepted` with a `Generation` in the `queued` state, plus a `Location` header pointing to the poll URL and a `Retry-After` hint:

    ```json theme={null}
    {
      "object": "generation",
      "id": "gen_4a7c2f9e8b1d",
      "status": "queued",
      "diagram": null,
      "image_url": null,
      "error": null,
      "created_at": "2026-06-07T09:15:00Z"
    }
    ```

    Now poll `GET /v1/generations/{id}` until `status` becomes `succeeded` (or `failed`). Honor the `Retry-After` hint between polls.

    <CodeGroup>
      ```bash curl theme={null}
      curl https://justflow.it/api/v1/generations/gen_4a7c2f9e8b1d \
        -H "Authorization: Bearer $JFI_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      async function poll(id) {
        while (true) {
          const res = await fetch(
            `https://justflow.it/api/v1/generations/${id}`,
            { headers: { Authorization: `Bearer ${process.env.JFI_API_KEY}` } }
          );
          const gen = await res.json();
          if (gen.status === "succeeded") return gen;
          if (gen.status === "failed") throw new Error(gen.error.message);
          await new Promise((r) => setTimeout(r, 2000));
        }
      }

      const done = await poll("gen_4a7c2f9e8b1d");
      console.log(done.image_url);
      ```

      ```python Python theme={null}
      import os
      import time
      import requests

      def poll(generation_id):
          url = f"https://justflow.it/api/v1/generations/{generation_id}"
          headers = {"Authorization": f"Bearer {os.environ['JFI_API_KEY']}"}
          while True:
              gen = requests.get(url, headers=headers).json()
              if gen["status"] == "succeeded":
                  return gen
              if gen["status"] == "failed":
                  raise RuntimeError(gen["error"]["message"])
              time.sleep(2)

      done = poll("gen_4a7c2f9e8b1d")
      print(done["image_url"])
      ```
    </CodeGroup>

    When the job succeeds, the `200` response populates `diagram` (the created, saved diagram) and `image_url` (the rendered image in the requested theme and format):

    ```json theme={null}
    {
      "object": "generation",
      "id": "gen_4a7c2f9e8b1d",
      "status": "succeeded",
      "diagram": {
        "object": "diagram",
        "id": "c4e8a1b2-5d6f-4a9c-8e30-1b2c3d4e5f60",
        "name": "Invoice approval",
        "folder_id": null,
        "organization_id": null,
        "created_by": "1f0c9a44-6b2d-4e8a-9c31-2d5f7e0a1b22",
        "created_at": "2026-06-07T09:15:04Z",
        "updated_at": "2026-06-07T09:15:04Z"
      },
      "image_url": "/api/v1/diagrams/c4e8a1b2-5d6f-4a9c-8e30-1b2c3d4e5f60/image?theme=light&format=png",
      "error": null,
      "created_at": "2026-06-07T09:15:00Z"
    }
    ```

    <Note>
      Already have a saved diagram and just need an image (or a different theme)? Call `GET /v1/diagrams/{id}/image` directly — it renders on demand and is cached, with no generation quota cost.
    </Note>
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/api-reference/authentication">
    Key formats, the `live` vs `test` prefix, and the full list of scopes.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/api-reference/errors">
    The error envelope, every `type`/`code` pairing, and `X-Request-Id` tracing.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/api-reference/rate-limits">
    The 120-req/60s burst, the 500-generations/30-day quota, and `RateLimit-*` headers.
  </Card>

  <Card title="Diagrams API" icon="diagram-project" href="/api-reference/diagrams">
    List, create, read, update, delete diagrams and render images on demand.
  </Card>
</CardGroup>
