Starter
£0
See it answer your own questions, free for 24 hours.
- 100 API requests
- Chat API with live local listings
- Full documentation and starter kit
- Basic support
- Redeem code required
API v1.1 · Live local listings
Nyata is powered by local organisations, for local people. Give your users real answers about free meals, support services, local events and offers, drawn from the live listings of verified local organisations. One REST endpoint, every city Nyata rolls out to.
curl https://nyataai.co.uk/external_api.php \
-H "Content-Type: application/json" \
-H "X-API-Key: $NYATA_API_KEY" \
-d '{
"model": "nyata-1",
"messages": [{
"role": "user",
"content": "Where can I get a free meal today?"
}]
}'
{
"choices": [{
"message": {
"role": "assistant",
"content": "Several free meal services are open today..."
}
}],
"nyata": {
"results": [{
"type": "charity_service",
"name": "Community Lunch Club",
"start_time": "12:00",
"open_now": true
}]
}
}
Pick a plan and pay securely with Stripe, or redeem a free-trial code. No account needed first.
Choose a planReady-to-run scripts for macOS, Linux, Windows, Python and Node.js, with nothing to install.
Paste your key into .env and run one command.
One click takes you to secure Stripe checkout. No account needed: your key is emailed the moment you pay. Card, Apple Pay and Google Pay.
£0
See it answer your own questions, free for 24 hours.
£10/month
Put live local answers in your website or app.
Key ready the moment you pay
£50/month
10× the requests of Basic for 5× the price, plus AI assistants.
Upgrading from Basic keeps your key
Custom
When answers must be instant, complete and guaranteed.
Custom volumes, contract and invoicing
Busy some weeks, quiet others? Buy credits once and use them whenever you like. 1 credit = 1 request, credits never expire, and you top up the same key any time, with an invoice every time.
No account needed: pay, and your key is emailed straight away. Chat API with live listings; AI assistants (MCP) come with Premium.
| What you get | Starter | Basic | Pay as you go | Premium | Enterprise |
|---|---|---|---|---|---|
| Price | £0 with a code | £10 a month | From £5, no expiry | £50 a month | Custom |
| Requests | 100 in 24 hours | 1,000 a month | What you buy | 10,000 a month | Unlimited |
| Cost per request | Free | 1p | 1p to 1.25p | 0.5p | Agreed with you |
| Chat API with live listings | ✓ | ✓ | ✓ | ✓ | ✓ |
| AI assistants (MCP) | — | — | — | ✓ | ✓ Latest, early access |
| Live web search, with sources | — | — | — | 100 a day | 500 a day |
| Newly approved organisations | Weekly | Weekly | Weekly | Weekly | Instantly, with alerts |
| Results per search | Up to 25 | Up to 25 | Up to 25 | Up to 25 | Up to 50, open-now, nearest-first |
| Dashboard | Basic | Basic | Basic | Advanced | Advanced |
| Usage history and logs | 7 days, 24 hours | 7 days, 24 hours | 7 days, 24 hours | 90 days, 30 days, CSV | 90 days, 30 days, CSV |
| Webhooks, Slack, team, IP controls | — | — | — | ✓ | ✓ |
| Alerts in the dashboard and by email | ✓ | ✓ | ✓ | ✓ | ✓ |
| Free sandbox test keys | ✓ | ✓ | ✓ | ✓ | ✓ |
| Support | Basic | Priority | 24/7 dedicated, SLA |
Payment is handled securely by Stripe. Your key appears straight after payment and is emailed to you. Already have a Nyata account? Use the same email at checkout and the key is added to it automatically. Events, session times and closures are live on every plan.
Local knowledge people can act on, behind an API that's easy to work with.
Answers about food banks, free meals, support services, events and community resources, from a model fine-tuned on local community information.
Answers come from local organisations listed and verified on Nyata, and every response includes those listings as structured results you can show as cards or on a map.
Documented status codes, a Retry-After header on rate limits and a distinct quota_exceeded error, so your integration can fail gracefully.
Registered with the UK Information Commissioner's Office (ZC042900). Every request is authenticated with your key and encrypted over HTTPS.
Track requests, quota and spend, test calls in the built-in console, and get alerts in the dashboard and by email. Premium adds 90-day analytics, 30-day logs, webhooks and team access.
Connect Nyata to Claude, ChatGPT, Cursor, VS Code and other MCP apps, and they can search verified local listings as tools. Included with Premium and Enterprise.
For what listings can't cover (transport changes, council notices, a venue's latest hours), assistants can search the web and get a short answer with its sources.
The API points people to the right service. Bookings and payments stay with providers, so partners keep their own customer relationships.
Everything you need to integrate Nyata AI into your application.
The Nyata AI API gives your application the same local knowledge that powers the Nyata app. Send a question in plain words and get back a reply, plus the live listings behind it: services from verified local organisations and community events, with days, times and addresses.
POST https://nyataai.co.uk/external_api.phpX-API-Key headercity or location to scope results.New in v1.1: live listings. Answers now come from the services and events listed on Nyata, every response includes them in nyata.results, and you can send the user's location to get the nearest first. See Live results.
New: Nyata in AI assistants. Nyata now runs an MCP server, so Claude, ChatGPT, Cursor, VS Code, Gemini CLI and other MCP apps can search Nyata's live listings directly. See Use with AI assistants (MCP).
Built for discovery. The API recommends services and tells people where to go. It doesn't make bookings or take payments; those stay with the provider or happen in the Nyata app.
Make your first request in a few minutes. Each quickstart is a single file with nothing to install.
Ready-to-run quickstarts for Bash, PowerShell, Python and Node.js, plus a Postman collection, an API spec and a settings template.
nyata-starter-kit/
├── README.md
├── .env.example
├── .gitignore
├── bash/quickstart.sh
├── windows/quickstart.ps1
├── python/quickstart.py
├── node/quickstart.mjs
├── postman/
│ └── nyata-api.postman_collection.json
└── spec/
└── nyata-api.spec.yaml
Copy it from your API dashboard. No key yet? Buy a plan or redeem a trial code.
Copy .env.example to .env, then paste your key after NYATA_API_KEY=.
# macOS / Linux
cp .env.example .env
# Windows
copy .env.example .env
Prefer environment variables? Set NYATA_API_KEY instead. It takes priority over .env.
Needs curl, which comes with macOS and most Linux distributions. Also works in WSL and Git Bash.
cd nyata-starter-kit
bash bash/quickstart.sh "Where can I find a food bank?"
#!/usr/bin/env bash
#
# Nyata AI API - quickstart for macOS, Linux, WSL and Git Bash
#
# Usage (from the starter-kit folder):
# bash bash/quickstart.sh "Where can I find a food bank?"
#
# Needs curl. If python3 or jq is installed, only the reply text is printed;
# otherwise you'll see the full JSON response.
#
# Your API key is read from the NYATA_API_KEY environment variable, or from a
# .env file in the current folder or the starter-kit folder.
set -euo pipefail
DEFAULT_API_URL="https://nyataai.co.uk/external_api.php"
DEFAULT_MODEL="nyata-1"
DASHBOARD_URL="https://app.nyataai.co.uk/developers"
MAX_RETRIES=4
KIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Read KEY=VALUE lines from a .env file. Variables that are already set win.
load_env() {
local file="$1" line key value
local pattern='^[[:space:]]*(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=[[:space:]]*(.*)$'
local double_quoted='^"(.*)"$'
local single_quoted="^'(.*)'\$"
[[ -f "$file" ]] || return 0
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%$'\r'}"
[[ "$line" =~ $pattern ]] || continue
key="${BASH_REMATCH[2]}"
value="${BASH_REMATCH[3]}"
value="${value%"${value##*[![:space:]]}"}"
if [[ "$value" =~ $double_quoted || "$value" =~ $single_quoted ]]; then
value="${BASH_REMATCH[1]}"
fi
if [[ -z "${!key:-}" ]]; then
export "$key=$value"
fi
done < "$file"
}
load_env "$PWD/.env"
load_env "$KIT_DIR/.env"
API_URL="${NYATA_API_URL:-$DEFAULT_API_URL}"
MODEL="${NYATA_MODEL:-$DEFAULT_MODEL}"
API_KEY="${NYATA_API_KEY:-}"
QUESTION="${*:-Where can I get a free meal today?}"
if [[ -z "$API_KEY" || "$API_KEY" == "your_api_key" ]]; then
echo "Add your API key first: copy .env.example to .env and paste your key," >&2
echo "or run: export NYATA_API_KEY=\"your_api_key\"" >&2
exit 1
fi
if ! command -v curl >/dev/null 2>&1; then
echo "This script needs curl. Install it, then try again." >&2
exit 1
fi
# Turn a string into a JSON string literal without extra tools.
json_string() {
local s
s="$(printf '%s' "$1" | tr -d '\001-\010\013\014\016-\037')"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\r'/\\r}"
s="${s//$'\t'/\\t}"
printf '"%s"' "$s"
}
BODY="{\"model\":$(json_string "$MODEL"),\"messages\":[{\"role\":\"user\",\"content\":$(json_string "$QUESTION")}],\"max_tokens\":300,\"temperature\":0.7}"
RESPONSE_FILE="$(mktemp)"
HEADERS_FILE="$(mktemp)"
trap 'rm -f "$RESPONSE_FILE" "$HEADERS_FILE"' EXIT
attempt=0
while true; do
if ! status="$(curl -sS "$API_URL" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-A "nyata-starter-kit/1.0 (bash)" \
--max-time 30 \
--data-binary "$BODY" \
-D "$HEADERS_FILE" -o "$RESPONSE_FILE" -w '%{http_code}')"; then
echo "Network error: could not reach $API_URL" >&2
exit 1
fi
# Retry rate limits (429) and server errors (5xx) with backoff.
if [[ "$status" == "429" || "$status" == 5* ]] && (( attempt < MAX_RETRIES )); then
delay="$(awk 'tolower($1) == "retry-after:" { gsub(/\r/, "", $2); print $2 }' "$HEADERS_FILE" | tail -n 1)"
if ! [[ "$delay" =~ ^[0-9]+$ ]]; then
delay=$(( 1 << attempt ))
fi
if (( delay > 60 )); then
delay=60
fi
echo "HTTP $status - retrying in ${delay}s..." >&2
sleep "$delay"
attempt=$(( attempt + 1 ))
continue
fi
break
done
if [[ "$status" == "200" ]]; then
if command -v python3 >/dev/null 2>&1 &&
PYTHONIOENCODING=utf-8 python3 -c 'import json, sys; print(json.load(sys.stdin)["choices"][0]["message"]["content"])' < "$RESPONSE_FILE" 2>/dev/null; then
exit 0
fi
if command -v jq >/dev/null 2>&1 && jq -er '.choices[0].message.content' < "$RESPONSE_FILE" 2>/dev/null; then
exit 0
fi
cat "$RESPONSE_FILE"
echo
exit 0
fi
case "$status" in
400) message="Bad request - check the model name and message format." ;;
401) message="Invalid or missing API key - check NYATA_API_KEY." ;;
403)
if grep -q "quota_exceeded" "$RESPONSE_FILE"; then
message="Monthly quota used up - check $DASHBOARD_URL or upgrade your plan."
else
message="This API key doesn't have access to the API."
fi
;;
429) message="Too many requests - slow down and try again shortly." ;;
5??) message="Nyata AI had a problem handling the request - try again in a moment." ;;
*) message="Unexpected response." ;;
esac
echo "Error $status: $message" >&2
if [[ -s "$RESPONSE_FILE" ]]; then
echo "Response: $(head -c 500 "$RESPONSE_FILE")" >&2
fi
exit 1
Works in Windows PowerShell 5.1, which comes with Windows 10 and 11, and in PowerShell 7. The -ExecutionPolicy Bypass flag applies to this one run only.
cd nyata-starter-kit
powershell -ExecutionPolicy Bypass -File .\windows\quickstart.ps1 "Where can I find a food bank?"
<#
.SYNOPSIS
Nyata AI API - quickstart for Windows PowerShell 5.1 and PowerShell 7+.
.DESCRIPTION
Sends one question to the Nyata AI API and prints the reply.
Your API key is read from the NYATA_API_KEY environment variable, or from
a .env file in the current folder or the starter-kit folder.
.EXAMPLE
powershell -ExecutionPolicy Bypass -File .\windows\quickstart.ps1 "Where can I find a food bank?"
#>
param(
[Parameter(Position = 0, ValueFromRemainingArguments = $true)]
[string[]]$Question
)
$ErrorActionPreference = "Stop"
$DefaultApiUrl = "https://nyataai.co.uk/external_api.php"
$DefaultModel = "nyata-1"
$DashboardUrl = "https://app.nyataai.co.uk/developers"
$MaxRetries = 4
# Windows PowerShell 5.1 needs TLS 1.2 switched on explicitly.
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
} catch { }
# Show characters such as the pound sign correctly in the console.
try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch { }
function Write-Problem {
param([string]$Message)
[Console]::Error.WriteLine($Message)
}
# Read KEY=VALUE lines from a .env file. Variables that are already set win.
function Import-DotEnv {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return }
foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) {
if ($line -match '^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$') {
$key = $Matches[1]
$value = $Matches[2] -replace '^([''"])(.*)\1$', '$2'
if (-not [Environment]::GetEnvironmentVariable($key)) {
[Environment]::SetEnvironmentVariable($key, $value)
}
}
}
}
function Get-ErrorBody {
param($ErrorRecord)
if ($ErrorRecord.ErrorDetails -and $ErrorRecord.ErrorDetails.Message) {
return $ErrorRecord.ErrorDetails.Message
}
try {
# Windows PowerShell 5.1 keeps the body on the response stream.
$stream = $ErrorRecord.Exception.Response.GetResponseStream()
if ($stream.CanSeek) { $stream.Position = 0 }
return (New-Object IO.StreamReader($stream, [Text.Encoding]::UTF8)).ReadToEnd()
} catch {
return ""
}
}
function Get-RetryDelay {
param($Response, [int]$Attempt)
$delay = [Math]::Min([Math]::Pow(2, $Attempt), 60)
try {
$value = $null
if ($Response.Headers -is [System.Net.WebHeaderCollection]) {
$value = $Response.Headers["Retry-After"]
} elseif ($Response.Headers.RetryAfter -and $null -ne $Response.Headers.RetryAfter.Delta) {
$value = [int]$Response.Headers.RetryAfter.Delta.TotalSeconds
}
if ("$value" -match '^\d+$') { $delay = [Math]::Min([int]"$value", 60) }
} catch { }
return [int]$delay
}
$kitRoot = Split-Path -Parent $PSScriptRoot
Import-DotEnv (Join-Path (Get-Location).ProviderPath ".env")
Import-DotEnv (Join-Path $kitRoot ".env")
$apiKey = "$env:NYATA_API_KEY".Trim()
$apiUrl = if ($env:NYATA_API_URL) { $env:NYATA_API_URL } else { $DefaultApiUrl }
$model = if ($env:NYATA_MODEL) { $env:NYATA_MODEL } else { $DefaultModel }
$text = ($Question -join " ").Trim()
if (-not $text) { $text = "Where can I get a free meal today?" }
if (-not $apiKey -or $apiKey -eq "your_api_key") {
Write-Problem "Add your API key first: copy .env.example to .env and paste your key,"
Write-Problem 'or run: $env:NYATA_API_KEY = "your_api_key"'
exit 1
}
$payload = @{
model = $model
messages = @(@{ role = "user"; content = $text })
max_tokens = 300
temperature = 0.7
} | ConvertTo-Json -Depth 5 -Compress
$requestArgs = @{
Uri = $apiUrl
Method = "Post"
Headers = @{ "X-API-Key" = $apiKey }
ContentType = "application/json; charset=utf-8"
Body = [Text.Encoding]::UTF8.GetBytes($payload)
UserAgent = "nyata-starter-kit/1.0 (powershell)"
TimeoutSec = 30
UseBasicParsing = $true
}
for ($attempt = 0; ; $attempt++) {
$failure = $null
try {
$response = Invoke-WebRequest @requestArgs
} catch {
$failure = $_
}
if (-not $failure) { break }
$errorResponse = $failure.Exception.Response
if (-not $errorResponse) {
Write-Problem "Network error: could not reach $apiUrl ($($failure.Exception.Message))"
exit 1
}
$status = [int]$errorResponse.StatusCode
$detail = Get-ErrorBody $failure
# Retry rate limits (429) and server errors (5xx) with backoff.
if (($status -eq 429 -or $status -ge 500) -and $attempt -lt $MaxRetries) {
$delay = Get-RetryDelay $errorResponse $attempt
Write-Problem "HTTP $status - retrying in ${delay}s..."
Start-Sleep -Seconds $delay
continue
}
$message = switch ($status) {
400 { "Bad request - check the model name and message format." }
401 { "Invalid or missing API key - check NYATA_API_KEY." }
403 {
if ($detail -match "quota_exceeded") { "Monthly quota used up - check $DashboardUrl or upgrade your plan." }
else { "This API key doesn't have access to the API." }
}
429 { "Too many requests - slow down and try again shortly." }
default {
if ($status -ge 500) { "Nyata AI had a problem handling the request - try again in a moment." }
else { "Unexpected response." }
}
}
Write-Problem "Error ${status}: $message"
if ($detail) { Write-Problem "Response: $($detail.Substring(0, [Math]::Min(500, $detail.Length)))" }
exit 1
}
# Decode as UTF-8 explicitly so characters such as the pound sign survive.
$json = [Text.Encoding]::UTF8.GetString($response.RawContentStream.ToArray())
$reply = $null
try { $reply = ($json | ConvertFrom-Json).choices[0].message.content } catch { }
if ($reply -is [string]) {
Write-Output $reply
exit 0
}
Write-Problem "Unexpected response: $json"
exit 1
Needs Python 3.8 or newer and uses only the standard library. On Windows, type py instead of python3.
cd nyata-starter-kit
python3 python/quickstart.py "Where can I find a food bank?"
#!/usr/bin/env python3
"""Nyata AI API - Python quickstart (standard library only, Python 3.8+).
Usage (from the starter-kit folder):
python3 python/quickstart.py "Where can I find a food bank?"
Your API key is read from the NYATA_API_KEY environment variable, or from a
.env file in the current folder or the starter-kit folder.
To call the API from your own code, copy the ask() function into your project
or import it: from quickstart import ask
"""
import json
import os
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
DEFAULT_API_URL = "https://nyataai.co.uk/external_api.php"
DEFAULT_MODEL = "nyata-1"
DASHBOARD_URL = "https://app.nyataai.co.uk/developers"
MESSAGES = {
400: "Bad request - check the model name and message format.",
401: "Invalid or missing API key - check NYATA_API_KEY.",
403: "This API key doesn't have access to the API.",
429: "Too many requests - slow down and try again shortly.",
}
class NyataError(Exception):
"""An API error that retrying won't fix. Has .status and .body."""
def __init__(self, status, body):
self.status = status
self.body = body
if status == 403 and "quota_exceeded" in body:
message = "Monthly quota used up - check %s or upgrade your plan." % DASHBOARD_URL
elif status >= 500:
message = "Nyata AI had a problem handling the request - try again in a moment."
else:
message = MESSAGES.get(status, "Unexpected response.")
super().__init__("Error %s: %s" % (status, message))
def ask(question, api_key, api_url=DEFAULT_API_URL, model=DEFAULT_MODEL,
max_tokens=300, temperature=0.7, max_retries=4):
"""Send one question to Nyata AI and return the reply text.
Retries rate limits (429) and server errors (5xx), honouring Retry-After.
Raises NyataError for other API errors.
"""
payload = json.dumps({
"model": model,
"messages": [{"role": "user", "content": question}],
"max_tokens": max_tokens,
"temperature": temperature,
}).encode("utf-8")
headers = {
"Content-Type": "application/json",
"X-API-Key": api_key,
"User-Agent": "nyata-starter-kit/1.0 (python)",
}
for attempt in range(max_retries + 1):
request = urllib.request.Request(api_url, data=payload, headers=headers, method="POST")
try:
with urllib.request.urlopen(request, timeout=30) as response:
status = response.status
raw = response.read().decode("utf-8", "replace")
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", "replace")
if (error.code == 429 or error.code >= 500) and attempt < max_retries:
delay = _retry_delay(error.headers.get("Retry-After"), attempt)
print("HTTP %s - retrying in %ss..." % (error.code, delay), file=sys.stderr)
time.sleep(delay)
continue
raise NyataError(error.code, body) from None
try:
return json.loads(raw)["choices"][0]["message"]["content"]
except (ValueError, KeyError, IndexError, TypeError):
raise NyataError(status, raw) from None
def _retry_delay(retry_after, attempt):
if retry_after and retry_after.strip().isdigit():
return min(int(retry_after.strip()), 60)
return min(2 ** attempt, 60)
def load_env():
"""Read KEY=VALUE lines from .env files. Variables already set win."""
kit_root = Path(__file__).resolve().parent.parent
for path in (Path.cwd() / ".env", kit_root / ".env"):
if not path.is_file():
continue
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
if key.startswith("export "):
key = key[len("export "):].strip()
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
value = value[1:-1]
if key:
os.environ.setdefault(key, value)
def main():
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(errors="replace")
load_env()
api_key = os.environ.get("NYATA_API_KEY", "").strip()
if not api_key or api_key == "your_api_key":
sys.exit("Add your API key first: copy .env.example to .env and paste your key,\n"
"or set the NYATA_API_KEY environment variable.")
question = " ".join(sys.argv[1:]).strip() or "Where can I get a free meal today?"
api_url = os.environ.get("NYATA_API_URL") or DEFAULT_API_URL
try:
reply = ask(question, api_key, api_url=api_url,
model=os.environ.get("NYATA_MODEL") or DEFAULT_MODEL)
except NyataError as error:
print(error, file=sys.stderr)
if error.body:
print("Response: %s" % error.body[:500], file=sys.stderr)
sys.exit(1)
except OSError as error:
reason = str(getattr(error, "reason", error))
print("Network error: could not reach %s (%s)" % (api_url, reason), file=sys.stderr)
if "CERTIFICATE_VERIFY_FAILED" in reason:
print("On macOS, run 'Install Certificates.command' from your Python folder "
"in Applications, then try again.", file=sys.stderr)
sys.exit(1)
print(reply)
if __name__ == "__main__":
main()
Needs Node.js 18 or newer. There's no npm install step.
cd nyata-starter-kit
node node/quickstart.mjs "Where can I find a food bank?"
#!/usr/bin/env node
// Nyata AI API - Node.js quickstart (Node.js 18+, no dependencies)
//
// Usage (from the starter-kit folder):
// node node/quickstart.mjs "Where can I find a food bank?"
//
// Your API key is read from the NYATA_API_KEY environment variable, or from a
// .env file in the current folder or the starter-kit folder.
//
// To call the API from your own code, copy ask() into your project or import it:
// import { ask } from './quickstart.mjs';
// Keep API calls on your server - never ship your API key in browser code.
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const DEFAULT_API_URL = 'https://nyataai.co.uk/external_api.php';
const DEFAULT_MODEL = 'nyata-1';
const DASHBOARD_URL = 'https://app.nyataai.co.uk/developers';
const MESSAGES = {
400: 'Bad request - check the model name and message format.',
401: 'Invalid or missing API key - check NYATA_API_KEY.',
403: "This API key doesn't have access to the API.",
429: 'Too many requests - slow down and try again shortly.',
};
// An API error that retrying won't fix. Has .status and .body.
export class NyataError extends Error {
constructor(status, body) {
let message = MESSAGES[status] ?? 'Unexpected response.';
if (status === 403 && body.includes('quota_exceeded')) {
message = `Monthly quota used up - check ${DASHBOARD_URL} or upgrade your plan.`;
} else if (status >= 500) {
message = 'Nyata AI had a problem handling the request - try again in a moment.';
}
super(`Error ${status}: ${message}`);
this.name = 'NyataError';
this.status = status;
this.body = body;
}
}
// Send one question to Nyata AI and return the reply text.
// Retries rate limits (429) and server errors (5xx), honouring Retry-After.
export async function ask(question, {
apiKey,
apiUrl = DEFAULT_API_URL,
model = DEFAULT_MODEL,
maxTokens = 300,
temperature = 0.7,
maxRetries = 4,
} = {}) {
const body = JSON.stringify({
model,
messages: [{ role: 'user', content: question }],
max_tokens: maxTokens,
temperature,
});
for (let attempt = 0; ; attempt += 1) {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
'User-Agent': 'nyata-starter-kit/1.0 (node)',
},
body,
signal: AbortSignal.timeout(30000),
});
const text = await response.text();
if (response.ok) {
let reply;
try {
reply = JSON.parse(text).choices[0].message.content;
} catch {
// Handled below.
}
if (typeof reply === 'string') return reply;
throw new NyataError(response.status, text);
}
const retryable = response.status === 429 || response.status >= 500;
if (retryable && attempt < maxRetries) {
const delay = retryDelay(response.headers.get('retry-after'), attempt);
console.error(`HTTP ${response.status} - retrying in ${delay}s...`);
await new Promise((done) => setTimeout(done, delay * 1000));
continue;
}
throw new NyataError(response.status, text);
}
}
function retryDelay(retryAfter, attempt) {
const value = (retryAfter ?? '').trim();
const seconds = /^\d+$/.test(value) ? Number(value) : 2 ** attempt;
return Math.min(seconds, 60);
}
// Read KEY=VALUE lines from .env files. Variables that are already set win.
function loadEnv() {
const kitRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
for (const file of [join(process.cwd(), '.env'), join(kitRoot, '.env')]) {
if (!existsSync(file)) continue;
for (const line of readFileSync(file, 'utf8').split(/\r?\n/)) {
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
if (!match) continue;
const [, key, rawValue] = match;
if (process.env[key] === undefined) {
process.env[key] = rawValue.replace(/^(['"])(.*)\1$/, '$2');
}
}
}
}
async function main() {
if (typeof fetch !== 'function') {
console.error('This script needs Node.js 18 or newer.');
process.exit(1);
}
loadEnv();
const apiKey = (process.env.NYATA_API_KEY ?? '').trim();
if (!apiKey || apiKey === 'your_api_key') {
console.error('Add your API key first: copy .env.example to .env and paste your key,');
console.error('or set the NYATA_API_KEY environment variable.');
process.exit(1);
}
const question = process.argv.slice(2).join(' ').trim() || 'Where can I get a free meal today?';
const apiUrl = process.env.NYATA_API_URL || DEFAULT_API_URL;
try {
const reply = await ask(question, {
apiKey,
apiUrl,
model: process.env.NYATA_MODEL || DEFAULT_MODEL,
});
console.log(reply);
} catch (error) {
if (error instanceof NyataError) {
console.error(error.message);
if (error.body) console.error(`Response: ${error.body.slice(0, 500)}`);
} else {
const reason = error.cause?.code ?? error.message;
console.error(`Network error: could not reach ${apiUrl} (${reason})`);
}
process.exit(1);
}
}
// Run main() only when this file is executed directly, not when imported.
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
await main();
}
The reply is printed in your terminal. If something goes wrong, the script explains the error and what to do about it.
Every request needs your API key in the X-API-Key header.
X-API-Key: your_api_keyFind your key on your API dashboard. New to Nyata? Get a key: you'll create an account, then buy a plan or redeem a free-trial code. The examples on this page read your key from the NYATA_API_KEY environment variable.
Keep your key secret. Call the API from your server, never from browser or mobile-app code, and keep keys out of version control. The starter kit's .gitignore already excludes .env. If a key is exposed, email support@nyataai.co.uk straight away.
All requests go to one endpoint:
https://nyataai.co.uk/external_api.php
| Header | Value |
|---|---|
Content-Type | application/json |
X-API-Key | Your API key |
Always use HTTPS, and send the request body as JSON.
Send a JSON body with these parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Required | Model identifier: nyata-1 |
messages | array | Required | The conversation so far, as message objects (see below). |
max_tokens | integer | Optional | Maximum length of the reply, in tokens. Default: 1000. |
temperature | number | Optional | From 0 to 1. Lower values give more focused replies. Default: 0.7. |
location | object | Optional | Where the person is: { "latitude": 51.45, "longitude": -2.58 }. Live results are sorted nearest first and include distance_km. New in v1.1. |
city | string | Optional | Limit live results to one city, written as your users would write it. New in v1.1. |
Send the model string exactly as shown. It's matched character for character, so don't shorten or change it.
| Field | Type | Description |
|---|---|---|
role | string | user for the person's question; assistant for earlier replies you include as context. |
content | string | The message text. |
{
"model": "nyata-1",
"messages": [
{ "role": "user", "content": "Where can I get a free lunch today?" }
],
"location": { "latitude": 51.45, "longitude": -2.58 },
"max_tokens": 300,
"temperature": 0.7
}
A successful request returns 200 and a JSON object:
{
"id": "nyata-abc123",
"object": "nyata.answer",
"created": 1793500000,
"model": "nyata-1",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Alright my lover! Community Lunch Club is on today from 12:00 to 14:00..."
},
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 410, "completion_tokens": 96, "total_tokens": 506 },
"api_usage": { "requests_used": 4, "requests_limit": 1000, "requests_remaining": 996 },
"nyata": {
"model": "nyata-1",
"founder": "Ibrahim Maina",
"version": "1.1",
"results": [
{
"type": "charity_service",
"id": 10,
"name": "Community Lunch Club",
"organisation": "Example Community Kitchen",
"organisation_type": "charity",
"verification": "verified",
"service_type": "meals",
"day": "Saturday",
"next_date": "2026-10-31",
"start_time": "12:00",
"end_time": "14:00",
"open_now": false,
"meal_period": "lunch",
"booking_required": false,
"address": "1 Example Street",
"postcode": "AB1 2CD",
"distance_km": 0.4,
"directions_url": "https://www.google.com/maps/search/?api=1&query=51.45,-2.58"
}
]
}
}
| Field | Description |
|---|---|
choices[0].message.content | The reply text. This is usually the only field you need. |
choices[0].finish_reason | Why the reply ended. stop means it finished normally. |
usage | Tokens used by the request: prompt_tokens, completion_tokens and total_tokens. |
nyata.results | The live listings behind the reply: services and events you can show as cards, a list or map pins. Empty when nothing matched. See Live results. |
api_usage | Your plan's requests used, limit and remaining after this request. |
id, created, model | The response's identifier, when it was created (Unix time) and the model that produced it. |
Nyata answers from live listings: services run by verified local organisations and community events listed on Nyata. Every response includes the listings behind the answer in nyata.results, so you can show them as cards, a list or pins on a map. The reply text only recommends places that appear in those results.
One API for every city. There are no per-city endpoints or keys. Send city or location and results are scoped to it. As Nyata rolls out city by city, new places appear automatically with no change to your code. When nothing is listed for a request, results is empty and the reply says so rather than guessing.
charity_service): free meals and food, showers, shelter, clothing, healthcare, support groups, employment help and emergency support.event): community events, workshops, celebrations, local offers and discounts.Only approved, active listings are returned, with public details only. The API is for discovery: it tells people where to go and when, and bookings stay with the organisation or in the Nyata app.
| Field | Type | Description |
|---|---|---|
type | string | Always charity_service. |
id | integer | Listing ID. |
name | string | Name of the service, e.g. a lunch club or shower session. |
organisation | string | The organisation that runs it. |
organisation_type | string | charity, community_group, religious, restaurant, business or government. |
verification | string | How far Nyata has verified the organisation: new, trusted or verified. |
service_type | string | meals, showers, shelter, clothing, healthcare, support_groups, employment, emergency or other. |
day | string | The weekday it runs. |
next_date | string | The next date it runs (YYYY-MM-DD, UK time). |
start_time, end_time | string | Session times, 24-hour HH:MM. |
open_now | boolean | Whether it is running right now. |
meal_period, food_type | string | For meals: breakfast, lunch, dinner, late_night or all_day, and what food is served. |
what_is_provided, description, notes | string | Short public details from the organisation. |
booking_required | boolean | If true, people need to book ahead with the organisation. |
location_name, address, postcode, city | string | Where it happens. |
latitude, longitude | number | Coordinates, when known. |
distance_km | number | Distance from the location you sent. Only present when you send one. |
phone, website | string | Public contact details for the service or organisation. |
directions_url | string | A maps link to the location. |
| Field | Type | Description |
|---|---|---|
type | string | Always event. |
id | integer | Listing ID. |
title, description | string | What it is. |
category | string | event, promotion, discount, ceremony, announcement, news or update. |
starts, ends | string | Start and end, YYYY-MM-DD HH:MM in UK time. ends may be missing for single-day events. |
happening_now | boolean | Whether it is on right now. |
discount | string | The offer, for discounts and promotions. |
location, city | string | Where it happens. |
latitude, longitude | number | Coordinates, when known. |
tickets | object | { "available": true, "price_gbp": 5 } when tickets are sold through Nyata. |
link, image_url | string | More information and an image, when the organiser added them. |
featured | boolean | Highlighted by the organiser or Nyata. |
Fields without a value are left out, so check that a field exists before you use it. New fields may be added in future versions without notice; existing ones won't be removed or renamed without advance notice.
const data = await response.json();
const reply = data.choices[0].message.content; // the answer, as text
const results = data.nyata?.results ?? []; // the listings behind it
for (const item of results) {
if (item.type === "charity_service") {
console.log(`${item.name} (${item.organisation}): ${item.day} ${item.start_time}-${item.end_time}, ${item.address}`);
} else if (item.type === "event") {
console.log(`${item.title}: ${item.starts}${item.location ? ", " + item.location : ""}`);
}
}
Any HTTP client works. Each example reads your key from the NYATA_API_KEY environment variable.
curl https://nyataai.co.uk/external_api.php \
-H "Content-Type: application/json" \
-H "X-API-Key: $NYATA_API_KEY" \
-d '{
"model": "nyata-1",
"messages": [{ "role": "user", "content": "Where can I find a food bank?" }],
"max_tokens": 300
}'
// Node.js 18+, on your server. Never expose your API key in browser code.
const response = await fetch('https://nyataai.co.uk/external_api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.NYATA_API_KEY,
},
body: JSON.stringify({
model: 'nyata-1',
messages: [{ role: 'user', content: 'Where can I find a food bank?' }],
max_tokens: 300,
}),
});
if (!response.ok) {
throw new Error(`Nyata API error ${response.status}: ${await response.text()}`);
}
const data = await response.json();
console.log(data.choices[0].message.content);
# pip install requests
import os
import requests
response = requests.post(
"https://nyataai.co.uk/external_api.php",
headers={"X-API-Key": os.environ["NYATA_API_KEY"]},
json={
"model": "nyata-1",
"messages": [{"role": "user", "content": "Where can I find a food bank?"}],
"max_tokens": 300,
},
timeout=30,
)
response.raise_for_status()
print(response.json()["choices"][0]["message"]["content"])
<?php
$ch = curl_init('https://nyataai.co.uk/external_api.php');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . getenv('NYATA_API_KEY'),
],
CURLOPT_POSTFIELDS => json_encode([
'model' => 'nyata-1',
'messages' => [['role' => 'user', 'content' => 'Where can I find a food bank?']],
'max_tokens' => 300,
]),
]);
$body = curl_exec($ch);
if ($body === false) {
throw new RuntimeException('Could not reach the Nyata API: ' . curl_error($ch));
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status !== 200) {
throw new RuntimeException("Nyata API error $status: $body");
}
$data = json_decode($body, true);
echo $data['choices'][0]['message']['content'], PHP_EOL;
Official Python and JavaScript SDKs are in development. Until they're released, the starter kit's ask() helpers give you retries and clear errors with no dependencies.
The API uses standard HTTP status codes.
| Status | Meaning | What to do |
|---|---|---|
200 OK | Success. | Read choices[0].message.content. |
400 Bad Request | Invalid parameters. | Check the model string and message format. Retrying the same request won't help. |
401 Unauthorized | Invalid or missing API key. | Check the X-API-Key header. |
403 Forbidden | The key doesn't have access, or your plan's monthly quota is used up (quota_exceeded). | Don't retry. Check your dashboard, upgrade, or wait for your quota to reset. |
429 Too Many Requests | You're sending requests faster than your plan allows. | Wait for the number of seconds in the Retry-After header, then retry. |
500 Server Error | Something went wrong on our side. | Retry with exponential backoff. |
403 or 429? A 403 with quota_exceeded means you've used your plan's full monthly allowance: upgrade, or wait for your billing cycle to reset. A 429 means you're within your quota but sending requests too quickly: slow down and retry.
Every plan has a monthly request allowance, shown under Pricing, plus a short-term rate limit that keeps the API responsive for everyone.
429, wait for the number of seconds in the Retry-After header, then retry.429 or 5xx responses, back off exponentially: 1s, 2s, 4s, 8s.403 with quota_exceeded, stop retrying and check your dashboard for your remaining quota and reset date.The starter-kit scripts already follow this strategy, so you can copy their ask() helpers into your own project.
The current API version is v1.1. The model string is versioned separately from the API itself.
nyata.results; new optional location and city request fields; the model identifier is now nyata-1 (the previous identifier still works); the object field now reads nyata.answer. Existing integrations keep working.We'll give advance notice before any breaking change to the endpoint, the request format or the model identifier, so you have time to update your integration.
Nyata runs a remote Model Context Protocol server. Connect it to Claude, ChatGPT, Cursor, VS Code, Windsurf, Gemini CLI, Codex or any other app that supports remote MCP, and the assistant can search Nyata's live listings as tools: community support, local services, events and provider details.
https://mcp.nyataai.co.uk/mcp
Authorization: Bearer for server-to-server use. MCP with an API key is part of the Premium and Enterprise plans; keys created before 8 November 2026 keep the access they already had.whats_new feed, open-now and nearest-first search, and up to 50 results. Events, times and closures are live on every plan.https://mcp.nyataai.co.uk/mcp as the server URL. There's no client ID or secret to enter.Once added, the connector works across Claude on web, desktop and mobile. Claude's free plan allows one custom connector. On Team and Enterprise plans, an owner adds it for the organisation and each member connects with their own Nyata account.
The Nyata plugin bundles the connector with guidance on answering local questions safely (999 first in an emergency). It holds no keys or secrets: everyone signs in with their own Nyata account.
An uploaded plugin is added to your own account only. If you've already added the custom connector above, remove it so Nyata isn't connected twice.
Add the server, then run /mcp inside Claude Code to sign in with your Nyata account:
claude mcp add --transport http nyata https://mcp.nyataai.co.uk/mcp
To use an API key instead of signing in:
claude mcp add --transport http nyata https://mcp.nyataai.co.uk/mcp \
--header "Authorization: Bearer $NYATA_API_KEY"
https://mcp.nyataai.co.uk/mcp as the server URL and choose OAuth.Developer mode is available on ChatGPT's paid plans; on Business and Enterprise an admin may need to allow custom connectors. ChatGPT moves this setting from time to time, so check under Settings if it isn't where described.
Or add it to ~/.cursor/mcp.json. Cursor asks you to sign in with Nyata the first time it connects.
{
"mcpServers": {
"nyata": {
"url": "https://mcp.nyataai.co.uk/mcp"
}
}
}
Or add it to .vscode/mcp.json in your project, then click Start above the server and allow the Nyata sign-in.
{
"servers": {
"nyata": {
"type": "http",
"url": "https://mcp.nyataai.co.uk/mcp"
}
}
}
In the Cascade panel, open MCPs, choose View raw config and add Nyata, then refresh. Cascade opens the Nyata sign-in on first use.
{
"mcpServers": {
"nyata": {
"serverUrl": "https://mcp.nyataai.co.uk/mcp"
}
}
}
Add the server, then run /mcp auth nyata inside Gemini CLI to sign in:
gemini mcp add --transport http nyata https://mcp.nyataai.co.uk/mcp
Add the server, then sign in:
codex mcp add nyata --url https://mcp.nyataai.co.uk/mcp
codex mcp login nyata
Nyata works with any app that supports remote MCP servers over Streamable HTTP with OAuth. Give it the server URL and sign in with your Nyata account when asked, or send your API key as Authorization: Bearer if the app only supports headers.
| Tool | What it does |
|---|---|
find_community_support | Free meals, food banks, housing, mental health, jobs and other support near a postcode or area, with opening times. |
find_local_services | Barbers, beauty, cleaning and taxis listed on Nyata. |
find_events | Upcoming community events, workshops and offers. |
get_provider_details | Full details for one listing, using an id from another result. |
ask_nyata | A plain-words local question, answered by Nyata. |
whats_new | Organisations newly approved on Nyata, newest first (Enterprise early access). |
web_search | Looks something up live on the web (transport changes, council notices, a place’s own site) and returns a short summary with sources. Clearly marked as not Nyata-verified. |
get_coverage | Which areas Nyata covers now and what it can search. |
Usage. With an API key, each successful tool call counts towards your plan's monthly allowance, except get_coverage, which is free. Signed-in accounts can search listings freely and have a daily limit on ask_nyata and on web_search (15 a day). With a key, web_search allows 100 a day on Premium and 500 on Enterprise. Both are limited to 60 calls a minute.
Keep your key out of shared configs. If you add your API key to an MCP client, keep that config out of version control. For anything shared with other people, use Nyata sign-in instead, so every user connects with their own account.
The starter kit contains everything below. You can also download each file on its own.
apiKey variable, then send.
Email support@nyataai.co.uk with the time of the request, the endpoint, the status code and the response body. Never include your full API key.
Answers to common questions are in the Help Center and FAQs.