Run a guarded SQL query
harbor_run_queryExecute read-only SQL queries against Harbor's business database with automatic validation, row limits, and PII masking to safely retrieve subscription data.
Instructions
Run a read-only SQL query against the Harbor business database.
Every query is parsed and checked before execution. It will be rejected if it:
is not a SELECT (no INSERT, UPDATE, DELETE, DROP, ATTACH, PRAGMA)
contains more than one statement
touches a table outside: customers, subscriptions, plans, invoices, tickets, usage_events
calls a file or extension function
would scan the whole invoices or usage_events table without an index
A LIMIT is added if you omit one, and lowered if you exceed 500.
Args:
sql (string): one SQLite SELECT statement
limit (number, optional): row cap, 1-500, default 50
response_format ('markdown' | 'json'): default 'markdown'
Returns JSON: { "rows": object[], // result rows, PII columns partially masked "columns": string[], "row_count": number, "tables_read": string[], "limit_applied": number, "limit_adjusted": boolean, // true if we added or lowered your LIMIT "truncated": boolean, // true if the cap was hit and more data exists "masked_columns": string[], "duration_ms": number }
Examples:
"How many customers churned?" -> SELECT COUNT(*) FROM customers WHERE churned_at IS NOT NULL
"Revenue by plan" -> SELECT p.name, SUM(s.mrr_cents) FROM subscriptions s JOIN plans p ON p.id = s.plan_id GROUP BY p.name
Don't use for: changing data. Use harbor_issue_refund or harbor_extend_trial.
Errors are returned with a "How to fix" line. Read it — it names the specific column, table or clause that caused the rejection.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | A single SQLite SELECT statement. WITH ... SELECT is allowed. Anything else is rejected. | |
| limit | No | Row cap for this call. Defaults to 50, hard maximum 500. | |
| response_format | No | 'markdown' for a readable table, 'json' for machine-readable rows. | markdown |