# Question Endpoint

Ask a question against your Context Link and receive a short paragraph answer with citations. Unlike the [context endpoint](/docs/context-endpoint) (which returns the raw matching chunks), this endpoint runs retrieval then passes the context to a small LLM that composes a single concise response grounded in your sources.

> **Pro plan:** this endpoint is available on Pro plans only. Requests from non-Pro accounts return `402 Payment Required`.

## How to invoke it

### From an AI assistant (skill-driven)

With the `ask-question` skill installed, these phrases trigger it automatically:

- "ask context link what our refund policy is"
- "ask my docs when onboarding changed"
- "use context link to answer: …"
- "what does my knowledge base say about …"

In **Claude Code** specifically, the slash command `/ask-question` also invokes the skill directly.

Download the skill on the [Installation](/integrations) page for Claude Chat, Claude Cowork, or Codex.

### Two ways to call the endpoint directly

**1. Via your Context Link subdomain (simplest, paste into any AI chat):**

```
https://YOUR-SUBDOMAIN.context-link.ai/q/what-is-pricing?p=YOUR_PIN
https://YOUR-SUBDOMAIN.context-link.ai/question/what-is-pricing?p=YOUR_PIN
```

Both paths hit the same endpoint. Use dashes-for-spaces in the question slug. Response negotiates on `Accept` (defaults to HTML for ChatGPT / Gemini user agents, JSON or Markdown otherwise).

**2. Via the authenticated API (for servers and integrations):**

```
GET /api/v1/question?query=what-is-pricing
```

## Quick reference

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /api/v1/question` |
| **Authentication** | API key via `Authorization` header |
| **Rate limit** | 2 requests per 10 seconds |
| **Monthly cap** | Counts against your Pro LLM allowance (1,000 requests/month, shared across LLM-powered features) |
| **Response format** | JSON |
| **Cache duration** | 1 hour per unique query + mode combination |

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | Yes | The question to ask. Dashes and underscores are converted to spaces. |
| `mode` | string | No | Optional mode name to weight results (e.g. `customer-support`). |

### Status codes

| Code | Description |
|------|-------------|
| `200` | Answer returned (may be `no_context` when nothing relevant was found) |
| `400` | Query parameter is missing |
| `401` | API key is missing or subscription required |
| `402` | Pro plan required |
| `404` | API key is invalid |
| `429` | Rate limit exceeded, or monthly LLM allowance exceeded |
| `503` | LLM service temporarily unavailable, retry |

## Examples

### Basic usage

```bash
curl -X GET "https://context-link.ai/api/v1/question?query=what-is-our-refund-policy" \
     -H "Authorization: your-api-key-here"
```

**Response:**

```json
{
    "result": "ok",
    "answer": "Our refund policy allows full refunds within 30 days of purchase, no questions asked [1]. Refunds are processed back to the original payment method within 5 business days [2].",
    "citations": [
        { "n": 1, "title": "Refund policy", "url": "https://example.com/refunds" },
        { "n": 2, "title": "Billing operations SOP", "url": "https://notion.so/..." }
    ],
    "format": "markdown"
}
```

### No context found

When the query doesn't match any indexed content, `result` is `"no_context"` and `answer` is `null`:

```json
{
    "result": "no_context",
    "answer": null,
    "citations": [],
    "format": "markdown"
}
```

### LLM temporarily unavailable

If the upstream LLM call fails after retries, the endpoint returns HTTP `503` with:

```json
{
    "result": "llm_unavailable",
    "answer": null,
    "citations": [],
    "format": "markdown"
}
```

This is distinct from `no_context` so callers can retry rather than telling the end user "nothing found."

### With mode

```bash
curl -X GET "https://context-link.ai/api/v1/question?query=onboarding-checklist&mode=customer-support" \
     -H "Authorization: your-api-key-here"
```

### Python example

```python
import requests

api_key = "your-api-key-here"
response = requests.get(
    "https://context-link.ai/api/v1/question",
    params={"query": "what are our office hours"},
    headers={"Authorization": api_key}
)
data = response.json()
print(data["answer"])
for c in data["citations"]:
    print(f"[{c['n']}] {c['title']}: {c['url']}")
```

### JavaScript example

```javascript
const axios = require('axios');

axios.get('https://context-link.ai/api/v1/question', {
    params: { query: 'what are our office hours' },
    headers: { 'Authorization': 'your-api-key-here' }
}).then(r => {
    console.log(r.data.answer);
    r.data.citations.forEach(c => console.log(`[${c.n}] ${c.title}: ${c.url}`));
});
```

### Ruby example

```ruby
require 'net/http'
require 'json'
require 'uri'

uri = URI('https://context-link.ai/api/v1/question')
uri.query = URI.encode_www_form(query: 'what are our office hours')

req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'your-api-key-here'

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
puts data['answer']
```

## Subdomain form (details)

The `/q/{question}` and `/question/{question}` paths both hit the same endpoint on your subdomain. Use whichever reads better in context:

```bash
# JSON
curl -H "Accept: application/json" \
     "https://YOUR-SUBDOMAIN.context-link.ai/q/what-is-pricing?p=YOUR_PIN"

# Markdown (for pasting into docs)
curl -H "Accept: text/markdown" \
     "https://YOUR-SUBDOMAIN.context-link.ai/q/what-is-pricing?p=YOUR_PIN"

# Plain text (just the answer, no citations footer)
curl -H "Accept: text/plain" \
     "https://YOUR-SUBDOMAIN.context-link.ai/q/what-is-pricing?p=YOUR_PIN"
```

If you omit the `Accept` header and you're on ChatGPT or Gemini, you'll get HTML by default (they render Markdown poorly). All other agents get JSON.

## Response format

```json
{
    "result": "ok" | "no_context" | "llm_unavailable",
    "answer": "paragraph with inline [1] citations" | null,
    "citations": [{ "n": 1, "title": "...", "url": "..." }],
    "format": "markdown"
}
```

- `result`:
  - `"ok"`: an answer was produced from your context.
  - `"no_context"`: no relevant source material was found (HTTP 200). Don't retry.
  - `"llm_unavailable"`: retrieval succeeded but the LLM call failed (HTTP 503). Retry.
- `answer`: a single paragraph (≤ ~120 words), with inline `[N]` markers referencing the citations array. `null` when `result` is `"no_context"` or `"llm_unavailable"`.
- `citations`: only the sources the LLM actually used to compose the answer. Citation numbers are contiguous (`[1]`, `[2]`, `[3]` ...) and always resolve to an entry in this array.

## Error responses

```json
// 400 Bad Request
{ "message": "A query is required" }

// 402 Payment Required
{ "message": "The question endpoint is a Pro-plan feature." }

// 429 Too Many Requests (allowance)
{ "message": "Monthly question allowance exceeded. Resets at the start of next month." }

// 503 Service Unavailable
{ "result": "llm_unavailable", "answer": null, "citations": [], "format": "markdown" }
```
