np_live_, and the host it was issued
for. Everything else is one HTTP POST.
There is no SDK to install yet. The clients below are plain HTTP against the
published contract, which is exactly what the SDKs will wrap — so nothing you
write here is throwaway. See Integrations.
1. Set your credentials
export NEEDLEPATH_API_KEY="np_live_…"
export NEEDLEPATH_HOST="api.nextmoca.com"
import os
API_KEY = os.environ["NEEDLEPATH_API_KEY"]
HOST = os.environ["NEEDLEPATH_HOST"]
const API_KEY = process.env.NEEDLEPATH_API_KEY!;
const HOST = process.env.NEEDLEPATH_HOST!;
Keys are org-scoped and grant the ability to spend your organisation’s
allowance. Keep them server-side. Never put one in a browser bundle, a mobile
app, or a URL — and never in
request_id, which reaches our logs
(why).2. Check you can reach the service
curl -sS "https://$NEEDLEPATH_HOST/v1/health" \
-H "Authorization: Bearer $NEEDLEPATH_API_KEY"
{
"status": "ok",
"service": "needlepath-hosted-adapter",
"endpoint": "/v1/context/select",
"default_operating_point": "np-2026-07-r2",
"operating_points": ["np-2026-07-r1", "np-2026-07-r2"],
"build_id": "9f1c2b7e4a"
}
401 here means no usable Authorization header reached us; a 403 means
one did and it was refused. The difference is worth wiring separately — see
Errors.
3. Make a selection
Send every record you are considering, the task they must serve, and the budget they have to fit inside.curl -sS "https://$NEEDLEPATH_HOST/v1/context/select" \
-H "Authorization: Bearer $NEEDLEPATH_API_KEY" \
-H "content-type: application/json" \
-d '{
"request_id": "req-quickstart-0001",
"records": [
{
"id": "r-812",
"kind": "tool_result",
"title": "get_invoice(4471)",
"source": "billing-api",
"text": "{\"invoice_id\": 4471, \"total_cents\": 128400, \"status\": \"open\", \"issued\": \"2026-07-02\"}"
},
{
"id": "r-813",
"kind": "external_data",
"title": "Refund policy v4",
"source": "handbook",
"text": "Refunds are issued to the original payment method within 10 business days of approval."
},
{
"id": "r-814",
"kind": "llm_response",
"text": "I have pulled up the account. One moment."
}
],
"task": {
"prompt": "Can this invoice still be refunded?",
"tool_name": "answer_customer"
},
"budget": {
"max_context_tokens": 4000,
"operating_point": "np-2026-07-r2"
},
"render": true,
"return_per_record": true
}'
import os
import requests
API_KEY = os.environ["NEEDLEPATH_API_KEY"]
HOST = os.environ["NEEDLEPATH_HOST"]
# Your own records, in whatever shape you already hold them.
records = [
{
"id": "r-812",
"kind": "tool_result",
"title": "get_invoice(4471)",
"source": "billing-api",
"text": '{"invoice_id": 4471, "total_cents": 128400, "status": "open"}',
},
{
"id": "r-813",
"kind": "external_data",
"title": "Refund policy v4",
"source": "handbook",
"text": "Refunds are issued to the original payment method within 10 business days.",
},
{
"id": "r-814",
"kind": "llm_response",
"text": "I have pulled up the account. One moment.",
},
]
response = requests.post(
f"https://{HOST}/v1/context/select",
headers={
"Authorization": f"Bearer {API_KEY}",
"content-type": "application/json",
},
json={
"request_id": "req-quickstart-0001",
"records": records,
"task": {
"prompt": "Can this invoice still be refunded?",
"tool_name": "answer_customer",
},
"budget": {
"max_context_tokens": 4000,
# Always pin this. See /concepts/operating-points.
"operating_point": "np-2026-07-r2",
},
"render": True,
"return_per_record": True,
},
timeout=30,
)
response.raise_for_status()
result = response.json()
print(result["rendered_context"])
print(f"{result['records_selected']}/{result['records_available']} records, "
f"{result['tokens_saved']} tokens saved, "
f"{result['engine_latency_ms']:.1f} ms")
const API_KEY = process.env.NEEDLEPATH_API_KEY!;
const HOST = process.env.NEEDLEPATH_HOST!;
const records = [
{
id: "r-812",
kind: "tool_result",
title: "get_invoice(4471)",
source: "billing-api",
text: '{"invoice_id": 4471, "total_cents": 128400, "status": "open"}',
},
{
id: "r-813",
kind: "external_data",
title: "Refund policy v4",
source: "handbook",
text: "Refunds are issued to the original payment method within 10 business days.",
},
{
id: "r-814",
kind: "llm_response",
text: "I have pulled up the account. One moment.",
},
];
const response = await fetch(`https://${HOST}/v1/context/select`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
request_id: "req-quickstart-0001",
records,
task: {
prompt: "Can this invoice still be refunded?",
tool_name: "answer_customer",
},
budget: {
max_context_tokens: 4000,
// Always pin this. See /concepts/operating-points.
operating_point: "np-2026-07-r2",
},
render: true,
return_per_record: true,
}),
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) throw new Error(`needlepath ${response.status}`);
const result = await response.json();
console.log(result.rendered_context);
console.log(
`${result.records_selected}/${result.records_available} records, ` +
`${result.tokens_saved} tokens saved, ` +
`${result.engine_latency_ms.toFixed(1)} ms`,
);
4. Read the response
{
"request_id": "req-quickstart-0001",
"rendered_context": "[tool_result] get_invoice(4471)\n{\"invoice_id\": 4471, …}\n\n[external_data] Refund policy v4\nRefunds are issued to …",
"policy_version": "np-2026-07-r2",
"selected": [
{
"record_id": "r-812",
"kind": "tool_result",
"title": "get_invoice(4471)",
"source": "billing-api",
"score": 0.91,
"reason": "keyword_and_entity_match",
"excerpt": "{\"invoice_id\": 4471, \"total_cents\": 128400, \"status\": \"open\"}",
"excerpt_format": "plain",
"selected_tokens": 31
}
],
"tokens_before": 96,
"tokens_after": 49,
"tokens_saved": 47,
"records_available": 3,
"records_selected": 2,
"fallback_used": false,
"selection_error": null,
"engine_latency_ms": 11.4,
"budget_tokens": 4000,
"attempted_budget_tokens": [],
"reduction_ratio": 0.489,
"gate": { "engaged": true, "reason": "engage:needle", "signals": {} },
"format_metrics": {}
}
| Field | Read it as |
|---|---|
rendered_context | The block to put in your prompt. render: false is meant to suppress it and is currently ignored — discard it client-side if you assemble the prompt yourself from selected[]. |
selected[] | Which records made it, with a score and a reason. excerpt is your text verbatim. |
fallback_used | true means the engine stood aside and handed your context back essentially intact. A normal outcome, not an error. |
policy_version | The frozen configuration that actually ran. Log it with your results. |
tokens_before and tokens_after are the engine’s own accounting, useful
for seeing how much a selection moved. They are not the billing quantity and
cannot be recomputed from your payload — see
How to recompute your own bill.5. Fail open
This is the part to get right before you ship. Selection is an optimisation, so a failure must degrade to no optimisation — never to an empty prompt.def select_context(records, prompt, *, budget=4000):
"""Returns a rendered context block, or None to mean 'send everything'."""
try:
response = requests.post(
f"https://{HOST}/v1/context/select",
headers={"Authorization": f"Bearer {API_KEY}",
"content-type": "application/json"},
json={
"request_id": new_request_id(),
"records": records,
"task": {"prompt": prompt},
"budget": {"max_context_tokens": budget,
"operating_point": "np-2026-07-r2"},
},
timeout=10,
)
response.raise_for_status()
result = response.json()
except Exception:
# Timeout, non-2xx, unparseable body: send what you were going to send.
return None
# An empty selection is also a pass-through case, not a valid prompt.
if result.get("records_selected", 0) == 0 or not result.get("rendered_context"):
return None
return result["rendered_context"]
context = select_context(records, prompt) or render_everything(records)
async function selectContext(
records: unknown[],
prompt: string,
budget = 4000,
): Promise<string | null> {
try {
const response = await fetch(`https://${HOST}/v1/context/select`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
request_id: newRequestId(),
records,
task: { prompt },
budget: {
max_context_tokens: budget,
operating_point: "np-2026-07-r2",
},
}),
signal: AbortSignal.timeout(10_000),
});
if (!response.ok) return null;
const result = await response.json();
// An empty selection is a pass-through case, not a valid prompt.
if (!result.records_selected || !result.rendered_context) return null;
return result.rendered_context as string;
} catch {
return null;
}
}
const context = (await selectContext(records, prompt)) ?? renderEverything(records);
Do not retry a
403 — none of its causes are transient. Do retry a 429 with
exponential backoff and jitter, and a 5xx only if the request is unchanged.
Errors has the full table.Next
Records and tasks
What to put in each field, and what each one buys you.
Adaptive budget
Let the engine escalate its own budget instead of guessing one.
Limits
Rate, quota and the one exact ceiling.
Authentication
Key format, rotation, and what a leaked key means.