Universal API Client
BroadSQL can import an HTTP API's definition from a Bruno collection and call it directly, reusing the same workbench you already use for databases.
What this is for
Many day-to-day investigations involve both a database and the application/API in front of it. Universal API Client lets you import an existing Bruno collection once, then browse and call its endpoints from BroadSQL: no separate tool, no re-entering URLs, headers, or credentials.
Every commonly used HTTP method is executable: GET, HEAD, POST, PUT, PATCH, and DELETE. The one exception is OPTIONS, which is imported and visible like any other verb but cannot be executed; see Supported HTTP methods and What this release does not do.
An API does not have to come from Bruno at all. CONFIG API (see Configuring APIs with CONFIG API below) is a graphical window for building and maintaining an API entirely by hand: environments, base URLs, variables, authentication, folders, and endpoints, then calling RUN against whatever you configured, exactly as if it had been imported.
Supported HTTP methods
| Method | Execution |
|---|---|
GET | Supported |
HEAD | Supported |
POST | Supported |
PUT | Supported |
PATCH | Supported |
DELETE | Supported |
OPTIONS | Not supported |
Every method, supported or not, is imported, persisted, and shown by SHOW ENDPOINTS (see Browsing endpoints); only execution is restricted. Attempting to execute OPTIONS (or any other/unrecognized method) is refused before any network request, naming the method: see Executing an unsupported method or body.
Importing a Bruno collection
Export your collection from Bruno as a bundled OpenCollection YAML file, a single .yml file (this is the format Bruno itself produces when you choose a single-file export), then import it:
IMPORT API BRUNO "C:\bruno\my-api.yml" MYAPI;
MYAPI is the identifier you choose for this API inside BroadSQL. The command reports what was imported:
API imported: My Service
Environments: created 3, updated 0
Folders: created 12, updated 0
Endpoints: created 87, updated 0
Re-running the same command against an updated export of the same collection is safe: an endpoint, folder, or environment already known from a previous import is updated in place; a new one is added; nothing already imported is ever deleted just because it disappeared from the source file. You never need to re-enter anything by hand.
Directory-based (multi-file) Bruno projects are not read yet, only a single bundled YAML file.
Configuring APIs with CONFIG API
CONFIG API opens a graphical configuration window for the whole API catalog, the same way CONFIG does for database connections, including the same application icon and the same File > Exit menu convention. It is a configuration tool only, never an execution workbench: there is no Execute button, no response viewer, and no request history here. Everything described below can be set up entirely by hand, without ever importing a Bruno file, and everything imported through IMPORT API BRUNO is immediately visible and editable in this same window.
Windows only, and only while connected to $CDF, exactly like CONFIG.
The window is a list of APIs on the left; the selected API's configuration on the right, across five tabs:
- General: name, description, and (for an imported API) where it came from and when it was last imported.
- Environments: every environment this API can run against (Development, Staging, Production, and so on), each with its own Base URL and its own variables.
Duplicatecopies an environment, including its secrets, so the common "duplicate Development, then change the Base URL and credentials" workflow needs no re-entry of everything else. - Authentication: the API's default authentication, used by every endpoint that does not set its own and is not inside a folder that sets one.
- Variables & Headers: API-wide variables and headers, available to every endpoint.
- Endpoints: a folder tree on the left, with a live search field above it (filters by id, name, alias, folder, verb, or URL as you type; purely a view filter, it never changes or marks anything dirty), and an editor on the right for whichever folder or endpoint is selected. The endpoint editor's General section shows the endpoint's system-managed numeric ID (not editable) alongside its name, method, URL, folder, and alias, so you never need another command just to find the ID that
SHOW ENDPOINTandSYNTAXexpect: useSHOW ENDPOINT <id>(see Endpoint detail) to look it up from the CLI instead.
One Save for the whole window: API > Save, or Ctrl+S. Editing a field never saves anything by itself; the frame's title shows a trailing * and the Save menu item becomes enabled the moment something is genuinely unsaved, and both clear the instant it is saved. At most one object is ever unsaved at a time: the API-level form (General, Authentication, and Variables & Headers together count as one), the currently selected Environment, or the currently selected Endpoint/Folder, since moving between any two of them (including moving from, say, General to Environments) prompts Save, Discard, or Cancel first whenever the one being left is genuinely dirty. Opening and closing the window, or moving between tabs, untouched never prompts. The menu bar has three menus:
- API: New, Import..., Delete, then Save (
Ctrl+S) and Close. - Environment: New, Duplicate, Delete.
- Endpoint: New Folder, New Endpoint, Delete.
Structural actions (creating a new API, duplicating an environment, deleting anything, importing a Bruno collection) still take effect immediately, exactly as before; only what used to be four separate per-object "Save" buttons became this one Save, so a field edit anywhere in the window is never lost silently and is never saved somewhere you didn't expect.
Renaming a folder or an endpoint from the endpoint tree stages the new name into its editor (marking it unsaved) rather than renaming it immediately: it is persisted the same way any other field edit is, through Ctrl+S/API > Save, or through the Save/Discard/Cancel prompt if you navigate away first.
A secret value (a token, a password, a client secret) is always masked in every table; a "Show values" checkbox reveals it deliberately, the same convention BroadSQL already uses for database passwords.
Creating an API by hand
+ New API asks for an identifier and a name, then creates the API immediately. From there:
- Open the Environments tab and create an environment (for example,
Production), entering its Base URL. - Open the Authentication tab if the whole API shares one authentication scheme, or leave it set to
Inherit/Noneand configure authentication per folder or per endpoint instead. - Open the Endpoints tab, use
New Folderto group related endpoints if useful, thenNew Endpointto create one: name, HTTP method, URL (which may reference${baseUrl}or any other variable), thenCtrl+S(orAPI > Save).
No internal identifier, source key, or database table is ever exposed; everything is named the way you already name it in the GUI.
Base URL
The environment editor shows Base URL as its own prominent field, never buried in the variable table below it. Internally it is still the ordinary baseUrl variable every endpoint template can reference (${baseUrl}/users), so changing it in the GUI immediately changes what every endpoint in that environment resolves to, whether the endpoint's URL uses ${baseUrl} explicitly or was created as a bare relative path.
Authentication and Inherit versus None
Authentication can be set at three levels: the API itself, a folder, or one endpoint. Inherit means "use whatever the next level up defines" and is the default for a newly created folder or endpoint. None is different: it explicitly turns authentication off at that level, even if a folder or the API above it defines one. The GUI always shows which of the two is in effect; it is never left ambiguous.
Supported types: Basic, Bearer token, API key (header or query), and OAuth2 Client Credentials. An authentication scheme imported from Bruno that this release cannot execute (Digest, NTLM, and similar) is shown as "Unsupported", read only, with the original type named, so it is never silently discarded or misrepresented as "no authentication".
Endpoint aliases
Every endpoint may optionally carry a BroadSQL alias, a short stable name such as DO_ORDER, set in the endpoint's General tab. An alias is entirely separate from the endpoint's display name and from its Bruno import identity:
- It is never generated automatically, on import or otherwise. You decide which endpoints, if any, get one.
- It survives a Bruno re-import untouched, even when the re-import updates the endpoint's name, URL, method, or anything else about it.
- It must be a valid identifier (letters, digits, and underscores, not starting with a digit) and must be unique within the API it belongs to. The same alias may be reused by a different API (for example,
PINGMAILcan name one endpoint inDESKand an unrelated endpoint inCRMat the same time), since an alias is always looked up within one API: the active session's API, or the one named explicitly withAPI <apiId>inSHOW ENDPOINTS. - Deleting the endpoint frees its alias immediately for reuse elsewhere, even within the same API.
An alias is a discovery handle, not something RUN accepts: RUN takes a URL (see Running an endpoint (RUN) below). SHOW ENDPOINT, SYNTAX and HELP accept the alias in place of the numeric endpoint id, and TAB after RUN expands it to the endpoint's URL. It also exists for a possible future scripting statement that would call a configured endpoint by this name (for example, a future CALL DO_ORDER(...)); that statement does not exist yet in this release.
Importing and exporting Bruno YAML
Import Bruno YAML and Export Bruno YAML, at the bottom of the API list, do the same thing as IMPORT API BRUNO and the export described below, with a preview before import and a scope/secret choice before export.
Exporting to Bruno YAML
An API configured in BroadSQL, whether imported from Bruno or built entirely by hand, can be exported back to a real bundled OpenCollection YAML file that Bruno itself can open, through CONFIG API's Export Bruno YAML button (there is no separate CLI export command in this release). You choose which environments to include (all, by default) and whether to include secret values.
Secret values are excluded by default. A secret variable, header, or credential is written to the file without its value at all, never as an empty string and never as a fake masked value standing in for the real one; every non-secret part of the configuration exports normally. Including real secret values requires explicitly turning that on and confirming a warning that the file will then contain plaintext credentials.
An explicit None authentication round-trips correctly. Setting None (as opposed to leaving it on Inherit) exports as an explicit "no authentication" entry and comes back as None on re-import, never as an unrecognized/unsupported type: Inherit and explicit None remain distinguishable throughout.
The BroadSQL alias is never exported. OpenCollection has no field for it, and BroadSQL does not invent one, to avoid producing a file that looks like standard Bruno YAML but is not. An alias assigned in BroadSQL therefore survives a re-import into the same BroadSQL installation, but not a round trip through an exported file into a different installation.
A structured request body (form-urlencoded or multipart form data) is edited and re-exported as its underlying data rather than through a dedicated field-by-field table in this release; every field, value, and piece of metadata is still preserved exactly, only the editing experience is plainer than a purpose-built table would be.
If an imported endpoint's authentication cannot be represented exactly in OpenCollection (an unsupported type such as Digest), the export still proceeds and clearly lists which endpoint's authentication could only be approximated, rather than silently producing a file that looks correct but is not.
Environments
Every environment defined in the Bruno collection (Development, Staging, Production, ...) becomes a separate, selectable environment in BroadSQL. The same imported endpoint resolves differently depending on which environment you execute it under; changing environment never changes the endpoint definition itself.
SHOW API ENVIRONMENTS MYAPI;
lists every environment as a table:
+----+-------------+---------------------------------+
| ID | NAME | BASE URL |
+----+-------------+---------------------------------+
| 1 | Development | https://dev.example.com |
| 2 | Production | https://api.example.com |
+----+-------------+---------------------------------+
2 environments
Environment variables that are secret (tokens, client secrets, ...) are never displayed by any command.
Connecting to an API session
CONNECT API MYAPI:Development;
establishes an active API session context, independent of and coexisting with any SQL database connection:
API MYAPI connected using environment Development.
Once connected, RUN and SHOW ENDPOINTS (below) use this API and environment without repeating them on every command, though both also accept an explicit API <apiId> clause to target a different API without an active session at all (see below). CONNECT API never closes or replaces the current database connection; connecting to a database with CONNECT <id> never clears an active API context either: the two are entirely independent, exactly like BroadSQL's connection and login concepts already are for SQL alone.
The prompt shows both when both are active:
$CDF [API MYAPI:Development]>
and just the API context when no database connection is active:
[API MYAPI:Development]>
An unknown or inactive API/environment, or a malformed <api>:<environment> target (a missing colon, a missing API name, or a missing environment name), is refused with a clear message; the previous API context, if any, is left unchanged.
Deactivating the currently connected API afterward (through CONFIG API, in another window) does not clear the session context by itself, but it does not remain usable through it either: RUN and `SHOW ENDPOINTS` revalidate the API's active status on every call and refuse it, naming it as inactive. DISCONNECT API and reconnect once it has been reactivated.
DISCONNECT API;
clears only the active API session context. The database connection, if any, is completely unaffected:
$CDF [API MYAPI:Development]> DISCONNECT API;
$CDF>
If no API context is active, DISCONNECT API does nothing.
Browsing endpoints
SHOW ENDPOINTS;
lists every endpoint of the active CONNECT API context, as a compact catalog table:
+----+------+----------------+------------------+--------+
| ID | VERB | FOLDER | NAME | ALIAS |
+----+------+----------------+------------------+--------+
| 5 | GET | Users | Get User | |
| 6 | POST | Users | Create User | |
| 12 | GET | Users / Search | Advanced Search | SEARCH |
+----+------+----------------+------------------+--------+
3 endpoints
A long folder path, name, or alias may be shown ellipsized (...) in this catalog view for display only: the persisted value itself is never truncated. Use SHOW ENDPOINT <id> (below) to see any one endpoint's full detail, or SHOW ENDPOINTS MATCH <keyword> to find it. This table is deliberately compact: it no longer shows the URL or whether the verb is executable, though both remain visible either way (SHOW ENDPOINT <id> for the full URL, Supported HTTP methods for which verbs can execute).
Without an active API context, add API <apiId> to target that API directly, no session required:
SHOW ENDPOINTS API MYAPI;
Both forms accept the same optional filters, in either order, after API <apiId> (or first, when it is omitted):
SHOW ENDPOINTS VERB GET;
SHOW ENDPOINTS MATCH search;
SHOW ENDPOINTS VERB GET MATCH search;
SHOW ENDPOINTS API MYAPI VERB GET MATCH search;
VERB <verb> matches the HTTP method exactly, case-insensitively. MATCH <keyword> is a case-insensitive substring search across the folder path, name, and alias combined. A filter that matches nothing reports an empty result, never an error.
Every HTTP method is listed, regardless of whether it can execute; see Supported HTTP methods. An endpoint is never hidden merely because its verb isn't executable. The ID column (not the endpoint's display name, which is not guaranteed unique across folders) or, if one is set, the ALIAS column, is what you pass to SHOW ENDPOINT or SYNTAX; to run the endpoint, use its URL with RUN.
Endpoint detail (SHOW ENDPOINT)
SHOW ENDPOINT 5;
SHOW ENDPOINT PINGMAIL;
SHOW ENDPOINT findAll;
(ENDPOINT and SHEND are accepted as shorter synonyms for SHOW ENDPOINT.) The argument accepts a numeric id (global and unique across every API, no active CONNECT API session needed; the same resolver SYNTAX and HELP <alias> use), an alias, or a name (the latter two scoped to the active session's API). A name matching more than one endpoint is reported as an ambiguous-candidates table (ID | METHOD | FOLDER | NAME | ALIAS | PATH) instead of being guessed at; use the id or alias shown there instead.
The command shows one endpoint's complete detail as a vertical, plain-text view: top-level properties as plain Label : value lines, then a blank line and each populated section (Query parameters, Path parameters, Headers, Authentication), with only the individual entries inside a section prefixed - :
ID : 5
API : My Service
Folder : Users
Name : Get User
Alias : (none)
Method : GET
URL : ${baseUrl}/users/${userId}
Query parameters
- expand : ${expand} disabled
Path parameters
- userId : ${userId} enabled
Headers
- Accept : application/json
Authentication: Inherited
URL is the composed effective URL: the endpoint's base path plus every currently enabled query parameter (see Query parameters and the URL below). A disabled parameter never appears there, only in the "Query parameters" section with its own disabled marker, its definition still fully visible. A value flagged secret in CONFIG API is masked (******) here too, in both the composed URL and its own row, the same masking convention used everywhere else. Authentication names the type in effect (or Inherited), never a resolved credential value.
Executing an endpoint
RUN is URL-native: you connect to an API/environment once with CONNECT API, then run one or more relative URLs against that session, exactly the way you would type paths against a live server:
CONNECT API MYAPI:Development;
RUN /users/42;
resolves the URL, query and path parameters, headers, and request body (see Request bodies below) against the connected environment, matches it to the corresponding stored endpoint (for its authentication, headers, and body; see Running an endpoint (RUN) below for exactly how matching works), sends the request, and displays the result. By default the response is shown as a complete LIST view (see Result display: LIST and TABLE below):
API: My Service
Environment: Development
Endpoint: GET Get User
GET https://dev.example.com/users/42
HTTP 200
Duration: 143 ms
Content-Type: application/json
id : 42
name : Alice
A non-JSON textual response (plain text, HTML, XML) is shown as-is. A response BroadSQL cannot safely display as text is shown as size/content-type metadata only, never as raw bytes. Any HTTP status is displayed the same way: a successful write commonly returns 200, 201, 202, or 204, and a 204 No Content (typical for DELETE) is shown cleanly with no body section, never as an error. An HTTP error response (404, 401, 500, ...) is shown exactly like a successful one: the status and body are what you came to see. BroadSQL never confuses a real HTTP response, whatever its status, with a transport/network failure (a connection refused, a DNS failure, a timeout), which is reported separately and always before any status/body could exist.
If a value the request needs (a variable, a path segment) cannot be resolved, execution stops before any network request is made, naming exactly what is missing, unless that value belongs to a disabled query parameter, which is never resolved or sent at all (see Query parameters and the URL).
Request bodies
A POST/PUT/PATCH endpoint (or a DELETE endpoint that happens to need one) sends whatever body is configured for it in CONFIG API, interpolated through the same variable scope as the URL, query, path, and headers:
{
"name": "${CUSTOMER_NAME}",
"email": "${CUSTOMER_EMAIL}"
}
executes with ${CUSTOMER_NAME}/${CUSTOMER_EMAIL} replaced by their resolved values, exactly like everywhere else ${variable} is used: see Variables. BroadSQL substitutes the configured text; it does not parse or rebuild the body as a JSON object, so the result is exactly what the substitution implies. If an interpolated value happens to make the body invalid JSON (for example, a value containing an unescaped "), BroadSQL still sends it as configured, the same textual-substitution behavior already used for URLs and headers, not a new validation layer, and the server's own response reports the problem.
Only a body configured as plain JSON, text, XML, or a SPARQL query can currently be sent; this is what CONFIG API's Body tab calls the json/text/xml/sparql modes. A structured body (form-urlencoded or multipart-form) is stored and preserved losslessly (round-trips through Bruno import/export and remains editable in CONFIG API) but cannot be executed yet, see Executing an unsupported method or body. CONFIG API's structured-body editor remains the raw canonical JSON text described in Configuring APIs with CONFIG API; this release does not add a field-by-field editor for it.
A request body is entirely optional: an endpoint with no body configured sends none, regardless of its HTTP method. DELETE commonly has none, but a POST/PUT/PATCH with nothing configured sends none too, and a GET with a body configured (unusual, but not prevented) would send it. BroadSQL follows what is configured, not a stereotype about the verb.
If no Content-Type header is already configured, BroadSQL sets a sensible default for the body's mode (application/json, text/plain, application/xml, or application/sparql-query); an explicit, configured Content-Type header always takes precedence.
The request body itself is never echoed anywhere in BroadSQL's own output, only the response is shown, so a secret value resolved inside a body is sent to the server correctly but never appears on screen, in a log, or in generated documentation.
Running an endpoint (RUN)
RUN is the single command that executes an endpoint, and it is URL-native, not id/alias-based: it always takes a relative URL, matched against the currently connected API/environment, never an explicit API/ENV clause of its own.
RUN [HTTP_METHOD] <relative-url> [TABLE|RAW];
CONNECT API <api>:<environment>; must be run first; RUN fails with a clear message (No API is connected. Use CONNECT API <api>:<environment>; before RUN.) if nothing is connected, and there is no way to name a different API/environment for a single RUN call; reconnect first. Within that session:
CONNECT API MYAPI:Development;
RUN /users/42;
RUN DELETE /users/42;
HTTP_METHOD is optional and defaults to GET; any other configured method (DELETE, POST, PUT, PATCH, HEAD, OPTIONS, ...) is given explicitly, before the URL. The URL is matched against the connected API's stored endpoints by method and path shape (so ${baseUrl}/users/:id matches /users/42) to find the endpoint whose authentication, headers, and body configuration apply; there is no separate step to look up an id or alias first. An endpoint's id, alias (set in CONFIG API), or name is a completion/discovery shortcut only, never something RUN itself accepts as a target: RUN SEARCH; or RUN listActiveUsers; (a bare reference, not a URL) is rejected with a hint toward SYNTAX SEARCH; (to see the endpoint's actual URL shape; see Endpoint detail) or typing RUN SEARCH<TAB>/RUN listActiveUsers<TAB> when JLine completion is active (see activatejline), which expands the reference to its real URL before you run it. Most imported endpoints never have an alias set at all, only a name and a numeric id, and completion works from any of the three.
A :name path or query placeholder, and any literal segment, resolves the same way described in Scope and precedence below, including a ${name}/{{name}} reference typed directly in the URL itself, which now resolves against the connected API's active environment before endpoint matching even happens (so it can affect which endpoint a URL matches, not just the request sent to it). The optional trailing TABLE/RAW clause selects the response rendering, exactly as described above; the default remains a complete LIST view. Authentication, variable resolution, result rendering, and PULL API RESULT capture all behave the same way regardless of the endpoint's method or shape.
Query parameters and the URL
An endpoint's URL and its structured query-parameter list (both editable in CONFIG API's endpoint editor, and both visible in SHOW ENDPOINT <id>) are two views of the same request, kept in sync in both directions:
URL
${baseUrl}/products?limit=${limit}&expand=${expand}
Query parameters
limit ${limit} enabled
expand ${expand} enabled
Disabling a parameter removes it from the displayed URL immediately, without deleting its definition:
URL
${baseUrl}/products?limit=${limit}
Query parameters
limit ${limit} enabled
expand ${expand} disabled
Re-enabling it restores it to the URL. Editing the URL text directly works the other way: adding &sort=${sort} adds a new, enabled sort row to the parameter grid; changing limit=${limit} to limit=50 updates that row's value to 50; and **removing a parameter from the URL text disables the matching row rather than deleting it**, symmetric with the Enabled checkbox, so its definition and value are never lost by an accidental edit. Editing the URL and editing the parameter grid always stay consistent with each other: the composed URL displayed anywhere (the editor, SHOW ENDPOINT <id>) can never contradict what the structured parameters say is enabled.
A disabled query parameter is never resolved, validated, or sent. This is what makes disabling a parameter safe even when no value for it exists anywhere:
expand = ${expand}, disabled -- execution succeeds; ${expand} is never looked up
expand = ${expand}, enabled -- execution fails: "Unable to resolve variable: expand" (if ${expand} is undefined)
An enabled parameter behaves exactly like any other ${variable} reference: if it cannot be resolved, execution stops before any network request, naming the missing variable.
Result display: LIST and TABLE
An API response often has many fields, or fields much wider than a SQL row typically has. Unlike SQL, you cannot usually write SELECT id, name, status against an HTTP endpoint to narrow what comes back. **BroadSQL's default display is therefore a complete, structure-preserving list, never a guessed-at subset of columns:**
CONNECT API MYAPI:Development;
RUN /users;
API: My Service
Environment: Development
Endpoint: GET List Users
GET https://dev.example.com/users
HTTP 200
Duration: 98 ms
Content-Type: application/json
[1]
id : 1
name : Alice
[2]
id : 2
name : Bob
Every field returned by the server appears: **no field is ever hidden, selected, or inferred as "important"** by BroadSQL, regardless of how many fields there are or how wide the response is. A nested object becomes its own indented block under its field name; a nested array becomes indented [1]/[2]/... entries, each in turn showing an object's full fields or a scalar value inline. A long value is shown in full, never truncated or ellipsized, unlike the catalog tables SHOW ENDPOINTS/ SHOW ALL APIS/SHOW API ENVIRONMENTS use, which may ellipsize a long cell for a purely navigational display (see Browsing endpoints); an actual API result is never treated that way.
TABLE remains fully available, as an explicit choice, when a response is naturally table-shaped and you want it displayed that way:
RUN /users TABLE;
API: My Service
Environment: Development
Endpoint: GET List Users
GET https://dev.example.com/users
HTTP 200
Duration: 98 ms
Content-Type: application/json
id|name |
--|-----|
1 |Alice|
2 |Bob |
2 rows.
This applies to four shapes: an array of objects (one column per key, in first-seen order across every element; a row missing a key is shown with a blank cell rather than shifting the other columns), a single object (a one-row table), an empty array (a table with zero rows and zero columns), and an array of plain values such as numbers or strings (a single column named VALUE, one row per element). A nested object or array inside a cell is shown as compact JSON text, never split into extra columns or rows. Any other JSON shape, such as an array mixing objects with plain values, or a response that is not JSON at all, falls back to pretty-printed JSON (or raw text) even under TABLE. TABLE never truncates or omits a field either: a wide table from an explicit TABLE request is expected and shown in full.
To always see the pretty-printed JSON or raw text, regardless of shape, append RAW instead:
RUN /users RAW;
{
"id": 1,
"name": "Alice"
}
LIST, TABLE, and RAW never discard the original response body; the underlying data is exactly what the server returned, whichever mode displays it. There is no way today to select a subset of returned fields (a future, explicit SELECT-style projection is a possible direction, not implemented in this release); every mode described above shows the complete response.
Executing a write method
A POST/PUT/PATCH/DELETE endpoint executes exactly like a GET, using its configured method, request body, and headers:
CONNECT API MYAPI:Development;
SHOW ENDPOINTS VERB POST MATCH customer;
+----+------+-----------+-----------------+----------------+
| ID | VERB | FOLDER | NAME | ALIAS |
+----+------+-----------+-----------------+----------------+
| 21 | POST | Customers | Create Customer | CREATECUSTOMER |
+----+------+-----------+-----------------+----------------+
1 endpoint
RUN POST /customers;
API: My Service
Environment: Development
Endpoint: POST Create Customer
POST https://dev.example.com/customers
HTTP 201
Duration: 118 ms
Content-Type: application/json
id : 501
status : created
The request body configured for the matched Create Customer endpoint (for example {"name": "${CUSTOMER_NAME}"}) was interpolated and sent, exactly as described in Request bodies. Updating or removing the same resource works the same way, only the matched endpoint's own configured method/path/body changes what is sent:
RUN PATCH /customers/501;
PATCH https://dev.example.com/customers/501
HTTP 200
Duration: 96 ms
...
RUN DELETE /customers/501;
DELETE https://dev.example.com/customers/501
HTTP 204
Duration: 74 ms
Executing an unsupported method or body
RUN OPTIONS /users;
against an OPTIONS-method endpoint refuses cleanly, before any request reaches the target server:
Execution refused.
Method OPTIONS is not enabled for execution in this release.
The same happens for an endpoint whose body mode is form-urlencoded or multipart-form (see Request bodies):
Endpoint 'Upload Attachment' has a 'multipart-form' request body, which this release cannot execute
(only json/text/xml/sparql bodies can be sent). The endpoint and its body remain fully visible and
editable in CONFIG API.
In both cases, the endpoint stays fully visible and configurable in the catalog and in CONFIG API; only execution is refused.
Executing an inactive API, endpoint, or environment
Deactivating an API or an environment (via CONFIG API) leaves it fully visible for configuration/reactivation, but refuses RUN, naming it as inactive rather than as not found:
API 'MYAPI' is inactive. Reactivate it first (CONFIG API), or use SHOW ALL APIS to list active APIs.
Environment 'Development' for API 'MYAPI' is inactive. Reactivate it first (CONFIG API), or use
SHOW API ENVIRONMENTS MYAPI to check its status.
An inactive endpoint is different: since RUN matches a URL structurally against the connected API's endpoints (see Running an endpoint (RUN)), a deactivated endpoint is simply excluded from matching, exactly as if it did not exist:
No endpoint matches:
/customers/501
Use SHOW ENDPOINTS API MYAPI to see configured endpoints for this API.
Reactivate it from CONFIG API, or with the endpoint's Reactivate action, and RUN matches it again normally, provided its alias, if it has one, has not since been claimed by a different active endpoint (see "Endpoint aliases" above); a reactivation that would create a duplicate alias is refused the same way a conflicting save would be, leaving the endpoint inactive. SHOW ENDPOINT <id>/SYNTAX <id>, by contrast, can still look up an inactive endpoint by its numeric id directly; only RUN's URL matching excludes it.
Exporting API results
The last RUN result held in memory can be pulled into BroadSQL's normal export/local snapshot machinery with PULL API RESULT TO ..., the direct sibling of PULL / TO ..., which does the same for the last SQL query held in memory (see export.md, "Exporting an API execution result", for the full grammar and every destination kind). This capture is always the complete, tabular-shaped snapshot of the result (via the same shape rules TABLE mode uses; see Result display: LIST and TABLE), independent of which display mode was actually shown on screen for that call:
RUN /users;
PULL API RESULT TO WORKCOPY.USERS AS H2;
2 row(s) pulled into WORKCOPY.USERS (table dropped and recreated).
The same result can also be exported to a spreadsheet tab or a flat file:
PULL API RESULT TO REPORT.USERS AS XLSX;
PULL API RESULT TO USERS AS CSV;
This works for any tabular-shaped result, not only an array of objects like the example above: a single object becomes a one-row table, an array of plain values becomes a single VALUE column, and so on (see Result display: LIST and TABLE above for the exact shape rules). Every exported column is text (VARCHAR); there is no numeric/date typing for an API result this release, since the API result was never typed to begin with. MODE APPEND is not supported for this source, only a full MODE OVERWRITE (the default). AS JSON exports the flattened tabular shape (its columns), not the original raw API response body; the raw body is unaffected either way.
A result held in memory is always the last successful execution only: any later execution attempt that does not itself complete successfully (a write-method refusal, an unresolved variable, unsupported authentication, an inactive entity, or a network error) clears it immediately, so PULL API RESULT correctly reports nothing to export rather than silently re-exporting an earlier, unrelated result. This applies identically to every form of RUN, including one that fails only because no API session context is active (for example, right after DISCONNECT API):
PULL API RESULT TO WORKCOPY.USERS AS H2;
PULL API RESULT: no API execution result held in memory. Run RUN first.
COPY RESULT also consumes this same result, copying it to the system clipboard as tab-separated text ready to paste into Excel/Calc, exactly as it already does for the last SQL query. It takes no arguments, and always uses whichever of a SQL query and an API result actually ran most recently in the session, regardless of which type it is:
RUN /users;
COPY RESULT;
The row count of the tabular result is confirmed, and the data is ready to paste directly into Excel or Calc.
Authentication
Whatever authentication the Bruno collection defines is imported and works automatically; nothing needs to be reconfigured in BroadSQL. Authentication can be set on the whole API, on a folder (inherited by every endpoint inside it), or on one endpoint specifically; a more specific setting always overrides a less specific one, and an endpoint explicitly configured with no authentication is never given one it didn't ask for.
Supported and executable in this release:
- No authentication
- HTTP Basic
- Static Bearer token
- API key, in a header or a query parameter
- OAuth2 Client Credentials (the access token is fetched automatically, cached in memory, and refreshed before it expires, never written to disk)
A credential that references an environment variable (the normal Bruno pattern, e.g. a Bearer token set to a variable) resolves against whichever environment you selected; switching environment switches credentials with it.
Other authentication schemes a Bruno collection might use (Digest, NTLM, OAuth1, AWS Signature, OAuth2 Authorization Code or Password grants) are imported and preserved, but this release cannot execute them: attempting to run such an endpoint is refused, naming the original authentication type, rather than silently sending the request unauthenticated.
Variables
A variable is referenced inside a URL, header value, query/path parameter value, request body, or authentication property with:
${variableName}
for example ${baseUrl}/users?key=${KEY}. This is BroadSQL's canonical variable syntax: it is what CONFIG API, generated examples, and every command in this document use.
Bruno's own {{variableName}} mustache syntax is also accepted, for interoperability with content imported from, or destined for, Bruno collections:
{{variableName}}
A single URL, header, or parameter may freely mix both forms; both are resolved identically, and either fails the same way if the variable cannot be resolved: execution stops before any network request is made, naming exactly which variable is missing. New requests configured by hand in BroadSQL should use ${variableName}; {{variableName}} exists so a Bruno collection's own syntax keeps working end to end (import, edit, execute, export back to Bruno) without a lossy conversion step, not as a second style to choose between when authoring something new.
Variable names are matched case-sensitively. A variable saved as KEY is referenced as ${KEY}, not ${key}: a differently-cased reference is treated as a different, unresolved name.
${variableName}/{{variableName}} is one of four distinct placeholder forms BroadSQL's API client uses, each with its own syntax and its own resolution rule: the sections below cover the other three.
Runtime placeholders (:name)
A :name segment in a RUN URL, whether a path segment (/users/:id) or a query value (?limit=:limit), is a runtime placeholder, resolved fresh for that one RUN call, not a stored ${variableName} reference. It is looked up case-insensitively, in this order, stopping at the first match:
session VAR
-> current API environment variable
-> this endpoint's own persisted CONFIG API value
-> this endpoint's configured default
-> error: missing required parameter (if none of the above resolves it)
A literal value in the URL (/users/42) is used and validated as-is; :name placeholders and literal values can appear side by side in the same URL. See VAR below for setting the first (session) step of this chain, and Scope and precedence for how the second (API environment) step relates to ${variableName} templating's own, separate precedence chain.
Operating system environment variables (${ENV:NAME})
${ENV:NAME}
reads an operating-system environment variable at the moment the command runs: a different namespace from ${variableName} (no ENV: prefix), used only in a RUN URL or a VAR value, never inside a stored endpoint definition. An undefined OS environment variable fails explicitly, exactly like an undefined ${variableName}; it is never silently substituted with an empty string.
RUN /api/customer/${ENV:CUSTOMER_ID};
VAR ID=${ENV:CUSTOMER_ID};
Session variables (VAR)
VAR <name>=<value-expression>;
sets a temporary, session-only variable: the first, most specific step of the :name resolution chain above. <value-expression> may reference ${ENV:NAME} (resolved once, at assignment time); a value containing spaces must be double-quoted:
VAR ID=123;
VAR COUNTRY=FR;
VAR NAME="John Doe";
VAR ID=${ENV:CUSTOMER_ID};
VAR <name>=<value> PERSIST; does something different: instead of a session variable, it writes the value into the current API environment's own variable set (the same store CONFIG API's Environments tab edits, see Environments) by case-insensitive name, leaving every other field of an existing row (enabled state, secret flag, and so on) untouched. It then clears any session VAR of the same name, so the newly persisted environment value is what every subsequent command sees immediately, through the normal resolution chain above, with no stale session override left shadowing it. PERSIST requires an active CONNECT API session (there is otherwise no "current environment" to persist into); plain VAR name=value;, without PERSIST, never touches the database and works with or without a session:
CONNECT API MYAPI:Development;
VAR ID=123 PERSIST;
Scope and precedence
${variableName}/{{variableName}} templating (the form used inside a stored endpoint's own URL, headers, parameter values, request body, and authentication properties) has its own, separate scope chain from :name's runtime resolution above. A variable may be defined at several levels; the most specific one that defines a given name wins:
API variables
-> environment variables (the selected environment only)
-> folder variables (root to leaf, along the endpoint's folder chain)
-> endpoint variables
An API-level variable is the broadest scope: it is sometimes called a "global" variable for the API, since it applies regardless of which environment or folder the endpoint sits in. A variable defined again at a more specific scope (environment, then folder, then endpoint) simply shadows the same name at every broader scope; it does not need to be re-declared everywhere.
Headers and query/path parameters are not part of this variable-precedence chain: a header or parameter is resolved after variable substitution, in its own separate precedence (API default, then folder, then endpoint, then whatever authentication adds), unrelated to where the *variable values themselves* were defined. An enabled query/path parameter is resolved the same way any other ${variable} reference is; a disabled one is skipped entirely, see Query parameters and the URL.
A complete example
An API-level (global) variable:
KEY = DESK
An endpoint's query parameter:
key = ${KEY}
Executing that endpoint resolves the parameter to the variable's value:
...?key=DESK
Changing environment never changes this, unless an environment-level variable named KEY (which would override the API-level one for that environment) or a folder/endpoint-level one is also defined.
baseUrl
baseUrl is an ordinary environment-scope variable, not a separate mechanism: it is simply the variable name BroadSQL (and Bruno) use, by convention, for an environment's base server URL. Setting Base URL in the Environments tab of CONFIG API (see "Base URL" above) sets this variable; referencing ${baseUrl} (or {{baseUrl}}) inside an endpoint's URL resolves it exactly like any other variable. A manually-created endpoint that uses a bare relative path (/users, with no ${baseUrl} at all) has the environment's Base URL prefixed automatically instead; both styles resolve correctly.
Secrets
A variable marked secret (a token, a password, a client secret) is masked in every table BroadSQL shows, and is never printed by RUN, SHOW ENDPOINTS, SHOW ENDPOINT, or the CONNECT API confirmation message: only the API and environment names are shown there, never variable values. A secret is still fully usable in ${variable}/{{variable}} references; masking only affects display.
SQL and API together
A database connection and an API session context can be active at the same time, and BroadSQL keeps running ordinary SQL against the database connection throughout; connecting to an API never closes or replaces it:
$CDF> CONNECT API DESK:PROD;
API DESK connected using environment PROD.
$CDF [API DESK:PROD]> RUN /api/rest/emails/v1/ping?key=DESK;
API: Desk Definition API
Environment: PROD
Endpoint: GET Check email service
GET https://desk.example.com/api/rest/emails/v1/ping?key=DESK
HTTP 200
Duration: 143 ms
OK
$CDF [API DESK:PROD]> SELECT COUNT(*) FROM CUSTOMER;
COUNT(*)
--------
1241
$CDF [API DESK:PROD]> PULL API RESULT TO PING_RESULT AS CSV;
1 row(s) pulled into PING_RESULT.csv.
$CDF [API DESK:PROD]> DISCONNECT API;
$CDF>
The same coexistence holds for a write endpoint: the database connection is no more affected by a POST/PATCH/DELETE than by a GET:
$CUSTOMERS_DB> CONNECT API CRM:TEST;
API CRM connected using environment TEST.
$CUSTOMERS_DB [API CRM:TEST]> RUN POST /customers;
API: CRM Service
Environment: TEST
Endpoint: POST Create Customer
POST https://crm-test.example.com/customers
HTTP 201
Duration: 118 ms
id : 501
status : created
$CUSTOMERS_DB [API CRM:TEST]> PULL API RESULT TO api_customer.CUSTOMERS AS H2;
1 row(s) pulled into api_customer.CUSTOMERS (table dropped and recreated).
$CUSTOMERS_DB [API CRM:TEST]> SELECT * FROM api_customer.CUSTOMERS;
ID |STATUS |
---|-------|
501|created|
$CUSTOMERS_DB [API CRM:TEST]> DISCONNECT API;
$CUSTOMERS_DB>
CUSTOMERS_DB, the SQL connection, is exactly as it was before CONNECT API ran; only the API session context changed.
What this release does not do
- Execute
OPTIONSendpoints, or any other/unrecognized HTTP method (imported and visible, not executable, whether imported or configured by hand inCONFIG API). - Execute an endpoint whose request body is configured as
form-urlencodedormultipart-form. Both are stored and preserved losslessly, and remain editable inCONFIG API, but cannot be sent yet; onlyjson/text/xml/sparqlbodies can be executed. See Request bodies. - Provide a field-by-field editor for a structured request body; it remains raw canonical JSON text in
CONFIG API. - Validate that an interpolated request body is syntactically valid JSON before sending it; the configured text is substituted and sent exactly as it resolves, the same way URL/header substitution already works.
- Execute anything at all from
CONFIG APIitself; it is a configuration tool only. - Prompt for confirmation before running a
POST/PUT/PATCH/DELETEendpoint; runningRUNagainst it is itself the instruction, exactly like every other command. - Automatically select, hide, or infer a subset of fields to display for an API result.
LIST, the default, always shows the complete response, andTABLE, when chosen explicitly, never drops a field either. See Result display: LIST and TABLE. An explicit, user-chosen field projection (conceptually similar to SQL'sSELECT id, name, status) is a possible future direction, not implemented in this release. - Run a future
CALL-style script statement using an endpoint alias; the alias exists today only as reserved, stable metadata for that future statement. - Import a directory-based (multi-file) Bruno project, only a single bundled YAML export.
- Import Postman collections or OpenAPI/Swagger specifications, or export to either format.
- Import or run Bruno scripts, pre/post-request automation, assertions, or tests.
- Relationalize or join an API result against another table server-side;
PULL API RESULT TO ... AS H2materializes it as a plain, unrelated table with text-only columns, ready for BroadSQL's normalJOINonce pulled, but no typing or relationalization happens automatically. - Automatically delete something from BroadSQL's catalog because it was removed from a re-imported collection; re-import only ever creates or updates, and the same applies to
CONFIG API's own manual editing. - Preserve a BroadSQL-assigned endpoint alias through an export followed by import into a different BroadSQL installation (it is never written to the exported file at all).
BroadSQL