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

# Connect a SQL database

> Give an agent live read (and scoped write) access to your own Postgres or MySQL database — safely, one column at a time.

This guide connects a **PostgreSQL** or **MySQL** database to an agent so it can answer from live
records on a call — for example "when's my appointment?" — using a least-privilege user, data
scoping, and a bound read query. Every step is one API call against `https://builder.flowyte.com/api/v1`.

<Note>
  Authenticate every request with a secret API key: `Authorization: Bearer flowyte_sk_…`. See
  [Authentication](/get-started/authentication) to mint one. Below, `KIND` is `postgres` or `mysql`.
</Note>

## What you'll wire up

| Action                    | Endpoint                                         | Scope                |
| ------------------------- | ------------------------------------------------ | -------------------- |
| Generate a scoped DB user | `GET /integrations/{kind}/sql/scripts`           | `integrations:read`  |
| Test the connection       | `POST /integrations/{kind}/sql/test`             | `integrations:write` |
| Connect                   | `POST /integrations/{kind}/connect`              | `integrations:write` |
| Discover the schema       | `POST /integrations/{kind}/discover`             | `integrations:write` |
| Scope out sensitive data  | `PATCH /integrations/{kind}/scoping`             | `integrations:write` |
| Bind a read as a skill    | `POST /agents/{id}/integrations/{kind}/bindings` | `skills:write`       |
| Publish                   | `POST /agents/{id}/publish`                      | `agents:write`       |

<Steps>
  <Step title="Create a least-privilege database user">
    Generate the setup SQL, then run it on your database as an admin. Never connect Flowyte with your
    admin credentials — create a scoped user instead.

    ```bash theme={null}
    curl "https://builder.flowyte.com/api/v1/integrations/postgres/sql/scripts?database=appdb" \
      -H "Authorization: Bearer $FLOWYTE_API_KEY"
    # → { "data": { "readOnly": { "sql": "CREATE ROLE flowyte_ro …" }, "generatedPassword": "…" } }
    ```

    For read-only lookups, run the `readOnly` script. Only add the scoped `write` script (pass
    `?tables=appointments,requests`) if the agent needs to insert or update rows.
  </Step>

  <Step title="Test the credentials">
    Validate before you store anything. The check runs reachability, TLS, auth, a read probe, a
    read-only-session proof, and a latency verdict, and returns an overall `ok`.

    ```bash theme={null}
    curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/sql/test \
      -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \
      -d '{ "credentials": {
            "host": "db.example.com", "port": "5432", "database": "appdb",
            "user": "flowyte_ro", "password": "…", "sslmode": "verify-full"
          } }'
    # → { "data": { "ok": true, "checks": [ … ] } }
    ```

    <Warning>
      `sslmode` must be `require`, `verify-ca`, or `verify-full` (`disable` is refused). A host that
      resolves to a private, loopback, or metadata address is refused too.
    </Warning>
  </Step>

  <Step title="Connect">
    Pass the same credentials to the connect endpoint. They're encrypted at rest and never returned.

    ```bash theme={null}
    curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/connect \
      -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \
      -d '{ "credentials": {
            "host": "db.example.com", "port": "5432", "database": "appdb",
            "user": "flowyte_ro", "password": "…", "sslmode": "verify-full"
          } }'
    # → { "data": { "status": "connected" } }
    ```
  </Step>

  <Step title="Discover the schema, then scope out anything sensitive">
    Introspect the database, then block tables or columns the agent should never see — they vanish from
    the schema browser and are refused at bind time.

    ```bash theme={null}
    curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/discover \
      -H "Authorization: Bearer $FLOWYTE_API_KEY"

    curl -X PATCH https://builder.flowyte.com/api/v1/integrations/postgres/scoping \
      -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \
      -d '{ "blockedColumns": { "customers": ["ssn", "card_last4"] } }'
    ```
  </Step>

  <Step title="Bind a lookup as a skill">
    Map a read onto a skill: pick the operation, map the caller's input to its filter, and project the
    columns you want back. It compiles to a frozen, parameterized query — the agent supplies the
    parameter and never sees raw SQL.

    ```bash theme={null}
    curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/integrations/postgres/bindings \
      -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \
      -d '{
            "toolName": "Look up an appointment",
            "description": "Find the caller'\''s appointment by phone and read back the time and status.",
            "operation": "appointments.read",
            "inputs": [ { "param": "caller_phone", "arg": "phone", "required": true } ],
            "projection": [
              { "path": ["appointment", "starts_at"], "leaf": "appointment_time" },
              { "path": ["appointment", "status"],    "leaf": "appointment_status" }
            ],
            "enabled": true
          }'
    ```

    <Tip>
      Don't want to hand-map? `POST /agents/{id}/integrations/postgres/bindings/auto` takes a
      plain-language goal ("look up the caller's next appointment by phone") and the AI assistant proposes
      the binding, saved as a draft to review.
    </Tip>
  </Step>

  <Step title="Publish and test">
    Binding edits the **draft**. Publish so live callers get real data, then try it in the simulator.

    ```bash theme={null}
    curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publish \
      -H "Authorization: Bearer $FLOWYTE_API_KEY"

    curl -N -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/simulate \
      -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \
      -d '{ "message": "Hi, can you tell me when my appointment is?", "draftMode": false }'
    ```
  </Step>
</Steps>

## Safety model

* **Least privilege** — you connect a scoped user, not an admin. Reads are read-only; writes are
  limited to the tables you granted.
* **Data scoping** — blocked tables and columns never reach the schema browser, the AI assistant, or
  a binding.
* **Frozen queries** — a binding compiles to one parameterized statement. The agent passes
  parameters; it can't run arbitrary SQL, and writes land disabled until you review them.

See the [SQL database connector](/integrations/sql-database) for the full endpoint reference.
