> Source: https://wexa.ai/docs/sdks-and-apis/rest

# REST API overview

The REST API is the surface with nothing to install: if you can make an HTTP request, you can call every Wexa tool. The two SDKs are thin clients over exactly these routes. [Authentication](/docs/get-started/authentication) explains the four credentials; for a first call, start at the [Quickstart](/docs/get-started/quickstart).

1. Read `GET /v1/connection-info`, which needs no credential, to learn the API base and the registered tools.

   ```bash
   curl -s "https://fabric.wexa.ai/v1/connection-info"
   ```

2. Send `Authorization: Bearer <credential>` with every other request.
3. Call a tool with `POST /v1/<tool-name>`, kebab-cased, and a JSON body of arguments.

   ```bash
   curl -s -X POST "https://fabric.wexa.ai/v1/query-context" \
     -H "Authorization: Bearer $WEXA_API_KEY" \
     -H 'Content-Type: application/json' \
     -d '{"query":"MATCH (c:Customer) WHERE c.project_id = $project_id RETURN c.name AS name LIMIT 5"}'
   ```

4. Read `lifecycle_id` and `result` from the response.
5. Pass the `lifecycle_id` to `GET /v1/lifecycles/{id}` to see how the call was governed.

## Limits

| Behavior | What happens |
| --- | --- |
| `GET` on a tool route | `405` with `Allow: POST`. |
| Unknown path | A JSON `404` with `content-type: application/json`, `{"error":"not_found","error_description":"no route for POST /v1/<tool>"}`. Both SDKs map it to `NotFound`. |
| Tool not configured on the deployment | The route exists and the call fails with an error naming what is missing: `<tool> is not configured (no data-service URL)`, `(no harness URL)` or `(needs the harness and data-service URLs)`. |
| Registered tools | `GET /v1/connection-info` lists them in a `tools` array. |
| Server-bound arguments | `project_id`, `projectID`, `organization_id` and `executed_by` come from your credential. Both SDKs refuse them before a request is built. |
| `POST /v1/docs` | Not a machine-readable specification. |

**Call a tool, and read the envelope**

### Call a tool

Every tool is `POST /v1/<tool-name>`, kebab-cased, with a JSON body. There are no path parameters, no query strings and no verb other than POST. `GET` on a tool route answers `405` with an `Allow: POST` header.

**Call a tool**

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.query_context(
    query="MATCH (c:Customer) WHERE c.project_id = $project_id RETURN c.name AS name LIMIT 5",
)
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.queryContext({
  query: "MATCH (c:Customer) WHERE c.project_id = $project_id RETURN c.name AS name LIMIT 5"
})
```

**REST API**

```bash
curl -s -X POST "https://fabric.wexa.ai/v1/query-context" \
  -H "Authorization: Bearer $WEXA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"query":"MATCH (c:Customer) WHERE c.project_id = $project_id RETURN c.name AS name LIMIT 5"}'
```

Per-tool arguments, returns and errors are on each tool's page, and [how every tool call works](/docs/sdks-and-apis/how-every-tool-call-works) covers what every call shares. A tool page's arguments are checked against the gateway's own registration and its responses are captured from real calls.

### Read the envelope

Every successful tool call returns the same two-key object, so there is no per-tool wrapper to learn.

```json
{
  "lifecycle_id": "qlc_000007",
  "result": { "nodes_merged": 1, "relationships_merged": 0, "ontology_extended": true, "new_labels": ["Customer"] }
}
```

`lifecycle_id` identifies the governed lifecycle the call ran as. `GET /v1/lifecycles/{id}` returns the stage-by-stage record: which stage passed, which refused and why. A refusal carries the same `lifecycle_id` in its error body, so a failure is traceable the same way a success is. `result` is the tool's own payload.

**Call a non-tool route**

### Call a non-tool route

Everything that is not a tool is conventional REST: `GET` for reads, plural collections, and sub-paths for actions.

```bash
curl -s "https://fabric.wexa.ai/v1/whoami"        -H "Authorization: Bearer $WEXA_API_KEY"
curl -s "https://fabric.wexa.ai/v1/approvals"     -H "Authorization: Bearer $WEXA_API_KEY"
curl -s "https://fabric.wexa.ai/v1/lifecycles/qlc_000002" -H "Authorization: Bearer $WEXA_API_KEY"
```

The gateway states the rule about itself. Calling the documentation tool and reading the `rules` array returns, verbatim:

```text
REST shape: every TOOL is `POST /v1/<tool-name>` in kebab-case; everything else (whoami, approvals,
audit, lifecycles, catalog) is conventional REST — GET for reads, plural collections, sub-paths for
actions
```

The non-tool routes:

| Route | What it is for |
|---|---|
| `GET /healthz` | Liveness. Unauthenticated. |
| `GET /v1/connection-info` | Discovery: the API base URL, the issuer, the registered tool list. Unauthenticated. |
| `GET /v1/whoami` | Who your credential says you are: user, role, organization, department, project, grants. |
| `POST /v1/apikeys`, `GET /v1/apikeys`, `DELETE /v1/apikeys/{id}` | Credential management. Requires a user token, not an API key. |
| `GET /v1/approvals`, `POST /v1/approvals/{id}/approve`, `POST /v1/approvals/{id}/reject` | The approval queue and its decisions. |
| `GET /v1/lifecycles/{id}` | The stage-by-stage record of one governed call. |
| `GET /v1/audit`, `GET /v1/audit/verify` | The audit record and its hash-chain verification. |
| `GET /v1/policy-decisions` | What the policy engine ruled, and when. |
| `GET` and `PUT /v1/projects/{projectId}/mode` | Read or change a project's mode. |
| `GET /v1/catalog/*` | The data catalog: assets, lineage, scorecards, connector status, sync statistics. |
| `/v1/codesync/*` | Repository registration, ingestion and job status for code sync. |
| `POST /v1/agents/{agentflowId}/chat/completions` | An OpenAI-compatible proxy onto a promoted agent version. |
| `/oauth/*` and `/.well-known/*` | OAuth 2.1 registration, authorization, token, and the two discovery documents. |

**Find your address, and check what your credential holds**

### Find your address with discovery

The only address you should hard-code is your workspace URL. `GET /v1/connection-info` is unauthenticated and tells you the rest, so the gateway decides its own address and moving it needs no client change.

```bash
curl -s "https://fabric.wexa.ai/v1/connection-info"
```

```json
{
  "api": { "base_url": "https://fabric.wexa.ai/v1" },
  "issuer": "https://fabric.wexa.ai",
  "workspace_header": "X-Wexa-Workspace",
  "tools": ["query-context", "create-ontology", "save-context", "docs", "search-code", "fetch-code"]
}
```

Both SDKs do this for you before their first call.

### Check what your credential holds

`POST /v1/docs` is a tool like any other. It takes a `topic` and returns prose and structured facts about your own scope. It is not an API specification: it serves no OpenAPI or Swagger document and returns no schema.

It is the fastest answer to "why did that call fail", because it tells you what your credential holds rather than what you meant it to hold.

```bash
curl -s -X POST "https://fabric.wexa.ai/v1/docs" \
  -H "Authorization: Bearer $WEXA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"topic":"governance"}'
```

The `result` contains your effective grants, your project's mode, your live quota headroom, the gateway's operating rules and your scope.

```json
{
  "grants": ["fabric:query.read", "fabric:docs.read", "fabric:ontology.write", "fabric:orchestrate.read",
             "fabric:orchestrate.write", "fabric:agent.run", "fabric:skill.write", "fabric:model.write"],
  "mode": "auto",
  "quota": { "org_window_max": 600, "org_window_used": 1, "project_window_max": 120, "project_window_used": 1 },
  "rules": ["..."],
  "scope": { "org_id": "org_c10", "project_id": "proj_c10", "role": "OWNER", "auth_kind": "api_key" }
}
```

### What's next

- [Authentication](/docs/get-started/authentication) to pick the credential for your caller.
- [Errors and retries](/docs/get-started/errors-and-retries) for status codes, error bodies and what is worth retrying.
- [Server-bound arguments](/docs/get-started/core-ideas#know-what-scope-a-call-carries) for why four argument names are never yours to send.
