diff --git a/api-reference/analytics.mdx b/api-reference/analytics.mdx
index c2879c7..335a271 100644
--- a/api-reference/analytics.mdx
+++ b/api-reference/analytics.mdx
@@ -1,11 +1,11 @@
---
title: "Analytics"
sidebarTitle: "GET /v1/analytics"
-description: "Aggregated analytics on comment trends, sentiment shifts, and engagement patterns."
+description: "Aggregated API usage analytics including request counts, costs, and cache performance."
api: "GET https://api.trynawa.com/v1/analytics"
---
-Retrieve aggregated analytics across your classified comments. This endpoint is **free**.
+Retrieve aggregated API usage analytics including request counts, costs, cache hit rates, latency, and endpoint/provider distribution. This endpoint is **free**.
## Request
@@ -13,19 +13,51 @@ Retrieve aggregated analytics across your classified comments. This endpoint is
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
-| `channel_id` | string | No | Filter by channel ID |
-| `platform` | string | No | Filter by platform |
| `from` | string | No | Start date (ISO 8601) |
| `to` | string | No | End date (ISO 8601) |
-| `group_by` | string | No | Group results by: `day`, `week`, `month`. Default: `day`. |
+| `group_by` | string | No | Group the breakdown by: `endpoint` (default), `day`, `provider`. |
### Example request
-```bash
-curl "https://api.trynawa.com/v1/analytics?platform=youtube&from=2025-01-01T00:00:00Z&to=2025-01-31T23:59:59Z&group_by=week" \
+
+
+```bash cURL
+curl "https://api.trynawa.com/v1/analytics?from=2026-04-01&to=2026-04-20&group_by=endpoint" \
-H "Authorization: Bearer nawa_test_sk_xxx"
```
+```typescript TypeScript
+import { Nawa } from '@nawalabs/sdk'
+
+const nawa = new Nawa({ apiKey: process.env.NAWA_API_KEY })
+
+const { data, error } = await nawa.analytics({
+ from: '2026-04-01',
+ to: '2026-04-20',
+ group_by: 'endpoint'
+})
+
+console.log(data.analytics.total_requests)
+console.log(data.analytics.cache_hit_rate)
+```
+
+```python Python
+from nawa import Nawa
+
+nawa = Nawa(api_key="your_api_key")
+
+result = nawa.analytics(
+ from_date="2026-04-01",
+ to_date="2026-04-20",
+ group_by="endpoint"
+)
+
+print(result.data.analytics.total_requests)
+print(result.data.analytics.cache_hit_rate)
+```
+
+
+
## Response
### Success response (200)
@@ -34,49 +66,94 @@ curl "https://api.trynawa.com/v1/analytics?platform=youtube&from=2025-01-01T00:0
{
"success": true,
"result": {
- "period": {
- "from": "2025-01-01T00:00:00Z",
- "to": "2025-01-31T23:59:59Z"
- },
- "total_comments": 4521,
- "summary": {
- "intent": {
- "question": 1205,
- "praise": 1890,
- "complaint": 678,
- "suggestion": 412,
- "spam": 198,
- "other": 138
- },
- "sentiment": {
- "positive": 2100,
- "negative": 890,
- "neutral": 1231,
- "mixed": 300
+ "analytics": {
+ "total_requests": 1234,
+ "total_cost": 7.404,
+ "cache_hits": 500,
+ "cache_hit_rate": 0.41,
+ "avg_latency_ms": 230,
+ "success_rate": 0.98,
+ "error_count": 25,
+ "endpoint_distribution": {
+ "/v1/classify": 800,
+ "/v1/translate": 200,
+ "/v1/detect": 150,
+ "/v1/moderate": 84
},
- "dialect": {
- "gulf": 2015,
- "egyptian": 1302,
- "levantine": 789,
- "msa": 415
+ "provider_distribution": {
+ "allam": 600,
+ "claude": 400,
+ "gemini": 234
},
- "toxicity": {
- "none": 3950,
- "mild": 320,
- "moderate": 180,
- "severe": 71
+ "period": {
+ "from": "2026-04-01",
+ "to": "2026-04-20"
}
},
- "trends": [
+ "by_endpoint": [
+ {
+ "endpoint": "/v1/classify",
+ "requests": 800,
+ "cost": 4.8,
+ "avg_latency_ms": 200
+ },
{
- "period": "2025-01-06/2025-01-12",
- "total": 1123,
- "sentiment_score": 0.72,
- "top_intent": "praise"
+ "endpoint": "/v1/translate",
+ "requests": 200,
+ "cost": 1.0,
+ "avg_latency_ms": 350
}
]
},
"errors": [],
- "request_id": "req_ana_abc123"
+ "request_id": "req_nw_ana_abc123"
}
```
+
+### Result fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `analytics.total_requests` | integer | Total API requests in the period |
+| `analytics.total_cost` | number | Total cost in USD |
+| `analytics.cache_hits` | integer | Number of requests served from cache |
+| `analytics.cache_hit_rate` | number | Ratio of cache hits to total requests (0-1) |
+| `analytics.avg_latency_ms` | integer | Average response latency in milliseconds |
+| `analytics.success_rate` | number | Ratio of successful (2xx) responses (0-1) |
+| `analytics.error_count` | integer | Number of error (4xx/5xx) responses |
+| `analytics.endpoint_distribution` | object | Request counts per endpoint |
+| `analytics.provider_distribution` | object | Request counts per AI provider |
+| `analytics.period` | object | Applied date filters (`from` and `to`, null if unset) |
+| `by_endpoint` | array | Breakdown grouped by the `group_by` parameter. Each entry has the group key, `requests`, `cost`, and `avg_latency_ms`. |
+
+
+ The breakdown key name changes based on `group_by`: `by_endpoint`, `by_day`, or `by_provider`.
+
+
+### Grouping examples
+
+
+
+ ```bash
+ curl "https://api.trynawa.com/v1/analytics?group_by=day&from=2026-04-15" \
+ -H "Authorization: Bearer nawa_test_sk_xxx"
+ ```
+
+ The response `by_day` array contains entries like:
+ ```json
+ {"day": "2026-04-15", "requests": 120, "cost": 0.72, "avg_latency_ms": 210}
+ ```
+
+
+
+ ```bash
+ curl "https://api.trynawa.com/v1/analytics?group_by=provider" \
+ -H "Authorization: Bearer nawa_test_sk_xxx"
+ ```
+
+ The response `by_provider` array contains entries like:
+ ```json
+ {"provider": "allam", "requests": 600, "cost": 3.0, "avg_latency_ms": 180}
+ ```
+
+
diff --git a/api-reference/comments-list.mdx b/api-reference/comments-list.mdx
index 1e18d08..ae70ec2 100644
--- a/api-reference/comments-list.mdx
+++ b/api-reference/comments-list.mdx
@@ -1,11 +1,11 @@
---
-title: "List Comments"
+title: "List comments"
sidebarTitle: "GET /v1/comments"
-description: "Retrieve and filter classified comments with pagination."
+description: "Retrieve and filter classified comments with offset-based pagination."
api: "GET https://api.trynawa.com/v1/comments"
---
-Retrieve classified comments with filtering and pagination. This endpoint is **free**.
+Retrieve previously classified comments with filtering and offset-based pagination. Returns comments stored from prior `/v1/classify` calls. This endpoint is **free**.
## Request
@@ -13,49 +13,53 @@ Retrieve classified comments with filtering and pagination. This endpoint is **f
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
-| `channel_id` | string | No | Filter by channel ID |
| `platform` | string | No | Filter by platform: `youtube`, `instagram`, `twitter`, `facebook` |
| `intent` | string | No | Filter by intent: `question`, `complaint`, `praise`, `suggestion`, `spam`, `other` |
| `sentiment` | string | No | Filter by sentiment: `positive`, `negative`, `neutral`, `mixed` |
| `dialect` | string | No | Filter by dialect: `gulf`, `egyptian`, `levantine`, `msa` |
-| `toxicity` | string | No | Filter by toxicity: `none`, `mild`, `moderate`, `severe` |
-| `from` | string | No | Start date (ISO 8601): `2025-01-01T00:00:00Z` |
-| `to` | string | No | End date (ISO 8601): `2025-01-31T23:59:59Z` |
-| `limit` | integer | No | Results per page (1–100). Default: 25. |
-| `cursor` | string | No | Pagination cursor from previous response. |
+| `limit` | integer | No | Results per page (1-100). Default: 25. |
+| `offset` | integer | No | Number of results to skip. Default: 0. |
### Example request
```bash cURL
-curl "https://api.trynawa.com/v1/comments?platform=youtube&intent=question&limit=10" \
+curl "https://api.trynawa.com/v1/comments?platform=youtube&intent=question&limit=10&offset=0" \
-H "Authorization: Bearer nawa_test_sk_xxx"
```
```typescript TypeScript
+import { Nawa } from '@nawalabs/sdk'
+
+const nawa = new Nawa({ apiKey: process.env.NAWA_API_KEY })
+
const { data, error } = await nawa.comments.list({
platform: 'youtube',
intent: 'question',
- limit: 10
+ limit: 10,
+ offset: 0
})
-// Auto-pagination
-for await (const comment of nawa.comments.list({ platform: 'youtube' })) {
- console.log(comment.text)
+for (const comment of data.comments) {
+ console.log(comment.text, comment.dialect)
}
```
```python Python
+from nawa import Nawa
+
+nawa = Nawa(api_key="your_api_key")
+
result = nawa.comments.list(
platform="youtube",
intent="question",
- limit=10
+ limit=10,
+ offset=0
)
-# Auto-pagination
-for comment in nawa.comments.list(platform="youtube"):
- print(comment.text)
+for comment in result.data.comments:
+ print(comment.text, comment.dialect)
```
@@ -70,24 +74,60 @@ for comment in nawa.comments.list(platform="youtube"):
"result": {
"comments": [
{
- "id": "cmt_abc123",
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "request_id": "req_nw_abc123",
"text": "متى الجزء الثاني؟",
"platform": "youtube",
"intent": "question",
"sentiment": "neutral",
"dialect": "gulf",
- "toxicity": "none",
- "created_at": "2025-01-15T10:30:00Z",
- "channel_id": "ch_123"
+ "created_at": "2026-04-20T10:30:00Z"
}
],
"pagination": {
- "has_more": true,
- "next_cursor": "cur_def456",
- "total_count": 1523
+ "total": 150,
+ "limit": 25,
+ "offset": 0,
+ "has_more": true
}
},
"errors": [],
- "request_id": "req_lst_abc123"
+ "request_id": "req_nw_lst_abc123"
}
```
+
+### Result fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `comments` | array | Array of classified comment objects |
+| `comments[].id` | string | Database record UUID |
+| `comments[].request_id` | string | Original request ID from the classify call |
+| `comments[].text` | string | Comment text |
+| `comments[].platform` | string | Source platform |
+| `comments[].intent` | string | Detected intent label |
+| `comments[].sentiment` | string | Detected sentiment |
+| `comments[].dialect` | string | Detected Arabic dialect |
+| `comments[].created_at` | string | ISO 8601 timestamp |
+| `pagination.total` | integer | Total number of matching comments |
+| `pagination.limit` | integer | Current page size |
+| `pagination.offset` | integer | Current offset |
+| `pagination.has_more` | boolean | Whether more results exist beyond the current page |
+
+### Pagination
+
+Use `offset` and `limit` to paginate through results. Increment `offset` by `limit` to fetch the next page:
+
+```bash
+# Page 1
+curl "https://api.trynawa.com/v1/comments?limit=25&offset=0" \
+ -H "Authorization: Bearer nawa_test_sk_xxx"
+
+# Page 2
+curl "https://api.trynawa.com/v1/comments?limit=25&offset=25" \
+ -H "Authorization: Bearer nawa_test_sk_xxx"
+```
+
+
+ Comment history storage is a recent feature. If no classifications have been stored yet, the endpoint returns an empty `comments` array with a helpful `message` field.
+
diff --git a/api-reference/comments-reply.mdx b/api-reference/comments-reply.mdx
index d6321d7..c8d89f4 100644
--- a/api-reference/comments-reply.mdx
+++ b/api-reference/comments-reply.mdx
@@ -1,60 +1,92 @@
---
-title: "Reply to Comment"
-sidebarTitle: "POST /v1/comments/:id/reply"
-description: "Generate a context-aware reply to a comment in Arabic or English."
-api: "POST https://api.trynawa.com/v1/comments/{id}/reply"
+title: "Reply to comment"
+sidebarTitle: "POST /v1/comments/reply"
+description: "Classify a comment and generate a context-aware reply in a single call."
+api: "POST https://api.trynawa.com/v1/comments/reply"
---
-Generate an AI-powered reply that matches the commenter's language and cultural context. For Arabic comments, replies match the detected dialect (Gulf, Egyptian, Levantine, MSA). For English comments, replies are natural and platform-appropriate. Language is auto-detected unless overridden.
+Classify a comment and generate an AI-powered reply in one request. For Arabic comments, replies match the detected dialect (Gulf, Egyptian, Levantine, MSA). For English comments, replies are natural and platform-appropriate. The response includes both the classification (intent, sentiment, priority) and the generated reply.
- Cost: **$0.008** per request (8 credits). Semantic cache hits are free (`X-NAWA-Cache: HIT`).
+ Cost: **$0.008** per request (8 credits). The default tone is `professional`.
## Request
-### Path parameters
+### Headers
+
+| Header | Required | Description |
+|--------|----------|-------------|
+| `Authorization` | Yes | `Bearer nawa_live_sk_xxx` or `Bearer nawa_test_sk_xxx` |
+| `Content-Type` | Yes | `application/json` |
+
+### Body parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
-| `id` | string | Yes | The comment ID to reply to. |
+| `text` | string | Yes | The comment text to classify and reply to. Must be a non-empty string. |
+| `tone` | string | No | Reply tone: `friendly`, `professional`, `casual`, `formal`. Default: `professional`. |
+| `max_length` | integer | No | Maximum reply length in characters (1-2000). Default: 500. |
+| `context` | object | No | Optional context with `platform` (string) and `brand_voice` (string) fields. |
-### Body parameters
+### Query parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
-| `tone` | string | No | Reply tone: `friendly`, `professional`, `casual`, `formal`. Default: `friendly`. |
-| `max_length` | integer | No | Maximum reply length in characters. Default: 500. |
-| `context` | string | No | Additional context about the channel or video to improve reply relevance. |
-| `language` | string | No | Force reply language: `ar`, `en`, `auto`. Default: `auto` (matches commenter's language). |
+| `provider` | string | No | Force a specific AI provider: `claude`, `gemini`, `allam`. |
### Example request
```bash cURL
-curl -X POST https://api.trynawa.com/v1/comments/cmt_abc123/reply \
+curl -X POST https://api.trynawa.com/v1/comments/reply \
-H "Authorization: Bearer nawa_test_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
+ "text": "متى الجزء الثاني؟",
"tone": "friendly",
- "context": "Tech review channel focused on smartphones"
+ "context": {
+ "platform": "youtube",
+ "brand_voice": "Tech review channel focused on smartphones"
+ }
}'
```
```typescript TypeScript
-const { data, error } = await nawa.comments.reply('cmt_abc123', {
+import { Nawa } from '@nawalabs/sdk'
+
+const nawa = new Nawa({ apiKey: process.env.NAWA_API_KEY })
+
+const { data, error } = await nawa.comments.reply({
+ text: 'متى الجزء الثاني؟',
tone: 'friendly',
- context: 'Tech review channel focused on smartphones'
+ context: {
+ platform: 'youtube',
+ brand_voice: 'Tech review channel focused on smartphones'
+ }
})
+
+console.log(data.classification.intent) // ["question"]
+console.log(data.reply.text) // Generated reply
```
```python Python
+from nawa import Nawa
+
+nawa = Nawa(api_key="your_api_key")
+
result = nawa.comments.reply(
- comment_id="cmt_abc123",
+ text="متى الجزء الثاني؟",
tone="friendly",
- context="Tech review channel focused on smartphones"
+ context={
+ "platform": "youtube",
+ "brand_voice": "Tech review channel focused on smartphones"
+ }
)
+
+print(result.data.classification.intent) # ["question"]
+print(result.data.reply.text) # Generated reply
```
@@ -67,15 +99,26 @@ result = nawa.comments.reply(
{
"success": true,
"result": {
- "comment_id": "cmt_abc123",
- "reply_text": "إن شاء الله الجزء الثاني قريب! تابعنا عشان ما يفوتك 🔔",
- "reply_dialect": "gulf",
- "tone": "friendly",
- "original_intent": "question",
- "original_dialect": "gulf"
+ "id": "rpl_nw_a1b2c3d4e5f6",
+ "object": "comment_reply",
+ "classification": {
+ "intent": ["question"],
+ "sentiment": "neutral",
+ "priority": "medium",
+ "requires_response": true
+ },
+ "reply": {
+ "text": "إن شاء الله الجزء الثاني قريب! تابعنا عشان ما يفوتك",
+ "direction": "rtl",
+ "tone": "friendly"
+ },
+ "provider": "claude",
+ "model": "claude-sonnet-4-5-20250929",
+ "cost_usd": 0.008,
+ "credits_used": 8
},
"errors": [],
- "request_id": "req_rep789xyz012"
+ "request_id": "req_nw_rep789xyz012"
}
```
@@ -83,9 +126,100 @@ result = nawa.comments.reply(
| Field | Type | Description |
|-------|------|-------------|
-| `comment_id` | string | The comment that was replied to |
-| `reply_text` | string | The generated reply text |
-| `reply_dialect` | string \| null | Dialect used in the reply (matches original). `null` for English replies. |
-| `tone` | string | The tone used for the reply |
-| `original_intent` | string | Detected intent of the original comment |
-| `original_dialect` | string | Detected dialect of the original comment |
+| `id` | string | Reply ID in `rpl_nw_xxx` format |
+| `object` | string | Always `"comment_reply"` |
+| `classification.intent` | string[] | Detected intent labels for the input comment |
+| `classification.sentiment` | string | `positive`, `negative`, `neutral`, or `mixed` |
+| `classification.priority` | string | `low`, `medium`, or `high` |
+| `classification.requires_response` | boolean | Whether the model judges this comment needs a reply |
+| `reply.text` | string | The generated reply text |
+| `reply.direction` | string | `"rtl"` for Arabic replies, `"ltr"` for English |
+| `reply.tone` | string | The tone used for the reply |
+| `provider` | string | AI provider that produced this result |
+| `model` | string | Specific model ID |
+| `cost_usd` | number | Always `0.008` for this endpoint |
+| `credits_used` | integer | Always `8` for this endpoint |
+
+### Response headers
+
+| Header | Description |
+|--------|-------------|
+| `X-Request-Id` | Unique request identifier |
+| `X-NAWA-Provider` | AI provider used |
+| `X-NAWA-Latency` | Processing time in milliseconds |
+| `X-RateLimit-Limit` | Per-minute request ceiling |
+| `X-RateLimit-Remaining` | Requests remaining in current window |
+| `X-RateLimit-Reset` | RFC 3339 timestamp when the window resets |
+
+### Error responses
+
+| Status | Type | When |
+|--------|------|------|
+| 400 | `invalid_request_error` | Missing or empty `text`, invalid `tone` or `max_length`, malformed JSON |
+| 401 | `authentication_error` | Invalid or missing API key |
+| 402 | `insufficient_credits` | Live key has insufficient credit balance |
+| 429 | `rate_limit_error` | Rate limit exceeded |
+| 500 | `api_error` | Internal or provider error |
+
+See [Errors](/errors) for the full envelope shape and all error codes.
+
+### More examples
+
+
+
+ ```bash
+ curl -X POST https://api.trynawa.com/v1/comments/reply \
+ -H "Authorization: Bearer nawa_test_sk_xxx" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "text": "The audio quality in this video is terrible, I can barely hear you",
+ "tone": "casual",
+ "max_length": 200
+ }'
+ ```
+
+ Response:
+ ```json
+ {
+ "success": true,
+ "result": {
+ "id": "rpl_nw_en01complaint",
+ "object": "comment_reply",
+ "classification": {
+ "intent": ["complaint"],
+ "sentiment": "negative",
+ "priority": "medium",
+ "requires_response": true
+ },
+ "reply": {
+ "text": "Sorry about that! We had a mic issue on this one. Already fixed for the next upload.",
+ "direction": "ltr",
+ "tone": "casual"
+ },
+ "provider": "claude",
+ "model": "claude-sonnet-4-5-20250929",
+ "cost_usd": 0.008,
+ "credits_used": 8
+ },
+ "errors": [],
+ "request_id": "req_nw_en01complaint"
+ }
+ ```
+
+
+
+ ```bash
+ curl -X POST https://api.trynawa.com/v1/comments/reply \
+ -H "Authorization: Bearer nawa_test_sk_xxx" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "text": "ما شاء الله عليك، محتوى رهيب!",
+ "tone": "friendly",
+ "context": {
+ "platform": "youtube",
+ "brand_voice": "Saudi food blogger, warm and appreciative"
+ }
+ }'
+ ```
+
+
diff --git a/api-reference/health.mdx b/api-reference/health.mdx
index ca110e8..8500ac7 100644
--- a/api-reference/health.mdx
+++ b/api-reference/health.mdx
@@ -1,62 +1,119 @@
---
-title: "Health Check"
+title: "Health check"
sidebarTitle: "GET /v1/health"
description: "Check the operational status of the NAWA API and its dependencies."
api: "GET https://api.trynawa.com/v1/health"
---
-Check the operational status of the NAWA API. This endpoint is **free** and does **not** require authentication.
+Check the operational status of the NAWA API and its dependencies. This endpoint is **free** and does **not** require authentication. Returns individual service status for the database, Redis cache, and the NAGL classification engine.
## Request
-```bash
+No parameters, no body, no authentication.
+
+
+
+```bash cURL
curl https://api.trynawa.com/v1/health
```
+```typescript TypeScript
+const response = await fetch('https://api.trynawa.com/v1/health')
+const health = await response.json()
+
+if (health.status === 'healthy') {
+ console.log('All systems operational')
+} else {
+ console.log('Degraded:', health.services)
+}
+```
+
+```python Python
+import requests
+
+response = requests.get("https://api.trynawa.com/v1/health")
+health = response.json()
+
+if health["status"] == "healthy":
+ print("All systems operational")
+else:
+ print(f"Degraded: {health['services']}")
+```
+
+
+
## Response
### Healthy response (200)
+Returned when all services are operational.
+
```json
{
- "success": true,
- "result": {
- "status": "healthy",
- "version": "1.0.0",
- "services": {
- "api": "operational",
- "classification": "operational",
- "database": "operational",
- "cache": "operational"
+ "status": "healthy",
+ "version": "v1",
+ "services": {
+ "database": {
+ "status": "healthy",
+ "latency_ms": 12
+ },
+ "redis": {
+ "status": "healthy",
+ "latency_ms": 3
},
- "timestamp": "2025-01-15T12:00:00Z"
+ "nagl": {
+ "status": "healthy"
+ }
},
- "errors": [],
- "request_id": "req_hlt_abc123"
+ "timestamp": "2026-04-20T12:00:00.000Z",
+ "latency_ms": 45
}
```
-### Degraded response (200)
+### Degraded response (503)
+
+Returned when one or more services are degraded or down. The HTTP status is `503`.
```json
{
- "success": true,
- "result": {
- "status": "degraded",
- "version": "1.0.0",
- "services": {
- "api": "operational",
- "classification": "degraded",
- "database": "operational",
- "cache": "operational"
+ "status": "degraded",
+ "version": "v1",
+ "services": {
+ "database": {
+ "status": "healthy",
+ "latency_ms": 15
+ },
+ "redis": {
+ "status": "down",
+ "latency_ms": 5000
},
- "timestamp": "2025-01-15T12:00:00Z"
+ "nagl": {
+ "status": "healthy"
+ }
},
- "errors": [],
- "request_id": "req_hlt_def456"
+ "timestamp": "2026-04-20T12:05:00.000Z",
+ "latency_ms": 5020
}
```
+### Result fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `status` | string | Overall status: `healthy` (all services up) or `degraded` (any service degraded or down) |
+| `version` | string | API version (always `v1`) |
+| `services.database.status` | string | Database connectivity: `healthy`, `degraded`, or `down` |
+| `services.database.latency_ms` | integer | Database query latency in milliseconds |
+| `services.redis.status` | string | Redis cache connectivity: `healthy`, `degraded`, or `down` |
+| `services.redis.latency_ms` | integer | Redis ping latency in milliseconds |
+| `services.nagl.status` | string | NAGL classification engine: `healthy` or `down` |
+| `timestamp` | string | ISO 8601 timestamp of the health check |
+| `latency_ms` | integer | Total health check processing time in milliseconds |
+
+
+ The `/v1/health` endpoint does not use the standard success envelope (`success`, `result`, `errors`, `request_id`). It returns the status object directly.
+
+
For real-time status monitoring, visit [status.trynawa.com](https://status.trynawa.com).
diff --git a/api-reference/rubric-classify.mdx b/api-reference/rubric-classify.mdx
index 4489175..c93c637 100644
--- a/api-reference/rubric-classify.mdx
+++ b/api-reference/rubric-classify.mdx
@@ -1,80 +1,103 @@
---
-title: "Rubric Classify"
-sidebarTitle: "POST /v1/rubric/classify"
-description: "Classify a comment against a custom rubric with predefined scoring criteria."
-api: "POST https://api.trynawa.com/v1/rubric/classify"
+title: "Rubric classify"
+sidebarTitle: "POST /v1/rubric-classify"
+description: "Classify text against a custom rubric with user-defined categories and scoring."
+api: "POST https://api.trynawa.com/v1/rubric-classify"
---
-Classify a comment against a custom rubric. Define your own categories and scoring criteria for domain-specific classification.
+Classify text against a custom rubric. Define your own categories with optional descriptions for domain-specific classification. Supports both single-label and multi-label classification with configurable confidence thresholds.
- Cost: **$0.003** per request (3 credits). **500 free requests/month** on this endpoint -- no credit card required. Semantic cache hits are free (`X-NAWA-Cache: HIT`).
+ Cost: **$0.003** per request (3 credits).
## Request
+### Headers
+
+| Header | Required | Description |
+|--------|----------|-------------|
+| `Authorization` | Yes | `Bearer nawa_live_sk_xxx` or `Bearer nawa_test_sk_xxx` |
+| `Content-Type` | Yes | `application/json` |
+
### Body parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
-| `text` | string | Yes | The comment text to classify. Max 5,000 characters. |
-| `rubric` | object | Yes | The rubric definition with categories and criteria. |
-| `rubric.categories` | string[] | Yes | List of category names to classify against. |
-| `rubric.descriptions` | object | No | Category descriptions to guide the model. Keys are category names, values are description strings. |
-| `platform` | string | No | Source platform for context. |
+| `text` | string | Yes | The text to classify. Must be a non-empty string. |
+| `rubric` | object | Yes | The rubric definition (see below). |
+| `rubric.categories` | object[] | Yes | Array of category objects (1-20). Each must have a non-empty `name`. |
+| `rubric.categories[].name` | string | Yes | Category name. |
+| `rubric.categories[].description` | string | No | Category description to guide the model. |
+| `rubric.multi_label` | boolean | No | Return all matching categories above the threshold. Default: `false` (single-label). |
+| `rubric.confidence_threshold` | number | No | Minimum confidence to include a category (0-1). Default: `0.5`. |
+
+### Query parameters
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `provider` | string | No | Force a specific AI provider: `claude`, `gemini`, `allam`. |
### Example request
```bash cURL
-curl -X POST https://api.trynawa.com/v1/rubric/classify \
+curl -X POST https://api.trynawa.com/v1/rubric-classify \
-H "Authorization: Bearer nawa_test_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"text": "وين الترجمة العربية؟ مافهمت شي",
"rubric": {
- "categories": ["translation_request", "technical_issue", "content_feedback", "off_topic"],
- "descriptions": {
- "translation_request": "User is asking for subtitles or translation",
- "technical_issue": "Audio, video, or playback problems",
- "content_feedback": "Comments about the content quality",
- "off_topic": "Not related to the video"
- }
- },
- "platform": "youtube"
+ "categories": [
+ {"name": "translation_request", "description": "User is asking for subtitles or translation"},
+ {"name": "technical_issue", "description": "Audio, video, or playback problems"},
+ {"name": "content_feedback", "description": "Comments about the content quality"},
+ {"name": "off_topic", "description": "Not related to the video"}
+ ],
+ "multi_label": false,
+ "confidence_threshold": 0.5
+ }
}'
```
```typescript TypeScript
+import { Nawa } from '@nawalabs/sdk'
+
+const nawa = new Nawa({ apiKey: process.env.NAWA_API_KEY })
+
const { data, error } = await nawa.rubric.classify({
text: 'وين الترجمة العربية؟ مافهمت شي',
rubric: {
- categories: ['translation_request', 'technical_issue', 'content_feedback', 'off_topic'],
- descriptions: {
- translation_request: 'User is asking for subtitles or translation',
- technical_issue: 'Audio, video, or playback problems',
- content_feedback: 'Comments about the content quality',
- off_topic: 'Not related to the video'
- }
- },
- platform: 'youtube'
+ categories: [
+ { name: 'translation_request', description: 'User is asking for subtitles or translation' },
+ { name: 'technical_issue', description: 'Audio, video, or playback problems' },
+ { name: 'content_feedback', description: 'Comments about the content quality' },
+ { name: 'off_topic', description: 'Not related to the video' }
+ ],
+ multi_label: false,
+ confidence_threshold: 0.5
+ }
})
```
```python Python
+from nawa import Nawa
+
+nawa = Nawa(api_key="your_api_key")
+
result = nawa.rubric.classify(
text="وين الترجمة العربية؟ مافهمت شي",
rubric={
- "categories": ["translation_request", "technical_issue", "content_feedback", "off_topic"],
- "descriptions": {
- "translation_request": "User is asking for subtitles or translation",
- "technical_issue": "Audio, video, or playback problems",
- "content_feedback": "Comments about the content quality",
- "off_topic": "Not related to the video",
- },
+ "categories": [
+ {"name": "translation_request", "description": "User is asking for subtitles or translation"},
+ {"name": "technical_issue", "description": "Audio, video, or playback problems"},
+ {"name": "content_feedback", "description": "Comments about the content quality"},
+ {"name": "off_topic", "description": "Not related to the video"},
+ ],
+ "multi_label": False,
+ "confidence_threshold": 0.5,
},
- platform="youtube",
)
```
@@ -88,23 +111,21 @@ result = nawa.rubric.classify(
{
"success": true,
"result": {
- "text": "وين الترجمة العربية؟ مافهمت شي",
- "category": "translation_request",
- "category_confidence": 0.94,
- "scores": {
- "translation_request": 0.94,
- "technical_issue": 0.03,
- "content_feedback": 0.02,
- "off_topic": 0.01
- },
- "dialect": "gulf",
- "dialect_confidence": 0.91,
+ "id": "rcl_nw_a1b2c3d4e5f6",
+ "object": "rubric_classification",
+ "categories": [
+ {"name": "translation_request", "confidence": 0.94}
+ ],
+ "multi_label": false,
"language": "ar",
- "model": "nagl-v1",
- "cached": false
+ "provider": "claude",
+ "model": "claude-sonnet-4-5-20250929",
+ "fallback_used": false,
+ "cost_usd": 0.003,
+ "credits_used": 3
},
"errors": [],
- "request_id": "req_rub123abc456"
+ "request_id": "req_nw_rub123abc456"
}
```
@@ -112,10 +133,83 @@ result = nawa.rubric.classify(
| Field | Type | Description |
|-------|------|-------------|
-| `category` | string | The top matching category from your rubric |
-| `category_confidence` | number | Confidence score (0–1) for the top category |
-| `scores` | object | Confidence scores for all rubric categories |
-| `dialect` | string | Detected Arabic dialect |
-| `dialect_confidence` | number | Dialect confidence score (0–1) |
-| `language` | string | Detected language code |
-| `cached` | boolean | Whether served from semantic cache |
+| `id` | string | Rubric classification ID in `rcl_nw_xxx` format |
+| `object` | string | Always `"rubric_classification"` |
+| `categories` | object[] | Matched categories above the confidence threshold. Each has `name` (string) and `confidence` (number, 0-1). |
+| `multi_label` | boolean | Whether multi-label mode was used |
+| `language` | string | Detected language: `ar`, `en`, or `mixed` |
+| `provider` | string | AI provider used: `claude`, `allam`, or `gemini` |
+| `model` | string | Specific model ID |
+| `fallback_used` | boolean | Whether a fallback provider produced this result |
+| `cost_usd` | number | Always `0.003` for this endpoint |
+| `credits_used` | integer | Always `3` for this endpoint |
+
+### Response headers
+
+| Header | Description |
+|--------|-------------|
+| `X-Request-Id` | Unique request identifier |
+| `X-NAWA-Provider` | AI provider used |
+| `X-NAWA-Latency` | Processing time in milliseconds |
+| `X-RateLimit-Limit` | Per-minute request ceiling |
+| `X-RateLimit-Remaining` | Requests remaining in current window |
+| `X-RateLimit-Reset` | RFC 3339 timestamp when the window resets |
+
+### Error responses
+
+| Status | Type | When |
+|--------|------|------|
+| 400 | `invalid_request_error` | Missing `text` or `rubric`, empty categories, category without `name`, more than 20 categories |
+| 401 | `authentication_error` | Invalid or missing API key |
+| 402 | `insufficient_credits` | Live key has insufficient credit balance |
+| 429 | `rate_limit_error` | Rate limit exceeded |
+| 500 | `api_error` | Internal or provider error |
+
+See [Errors](/errors) for the full envelope shape and all error codes.
+
+### Multi-label example
+
+When `multi_label` is `true`, the response includes all categories that meet the confidence threshold:
+
+```bash
+curl -X POST https://api.trynawa.com/v1/rubric-classify \
+ -H "Authorization: Bearer nawa_test_sk_xxx" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "text": "The audio is broken and there are no subtitles",
+ "rubric": {
+ "categories": [
+ {"name": "translation_request"},
+ {"name": "technical_issue"},
+ {"name": "content_feedback"},
+ {"name": "off_topic"}
+ ],
+ "multi_label": true,
+ "confidence_threshold": 0.3
+ }
+ }'
+```
+
+Response:
+```json
+{
+ "success": true,
+ "result": {
+ "id": "rcl_nw_multi_example",
+ "object": "rubric_classification",
+ "categories": [
+ {"name": "technical_issue", "confidence": 0.88},
+ {"name": "translation_request", "confidence": 0.72}
+ ],
+ "multi_label": true,
+ "language": "en",
+ "provider": "claude",
+ "model": "claude-sonnet-4-5-20250929",
+ "fallback_used": false,
+ "cost_usd": 0.003,
+ "credits_used": 3
+ },
+ "errors": [],
+ "request_id": "req_nw_rub_multi"
+}
+```
diff --git a/api-reference/translate.mdx b/api-reference/translate.mdx
index c10d2ba..96b0e9b 100644
--- a/api-reference/translate.mdx
+++ b/api-reference/translate.mdx
@@ -157,14 +157,14 @@ Translate text between English and Arabic with dialect awareness. Generates cult
```
-
- **Input:** "كيف حالك اليوم؟" (MSA)
- **Output:** "شلونك اليوم؟" (Gulf)
+
+ **Input:** "كتير حلو الفيديو هاد" (Levantine)
+ **Output:** "This video is really great"
```bash
curl -X POST https://api.trynawa.com/v1/translate \
-H "Authorization: Bearer nawa_test_sk_xxx" \
-H "Content-Type: application/json" \
- -d '{"text": "كيف حالك اليوم؟", "source": "ar", "target": "ar", "dialect": "gulf", "tone": "casual"}'
+ -d '{"text": "كتير حلو الفيديو هاد", "source": "ar", "target": "en", "tone": "formal"}'
```
@@ -173,8 +173,12 @@ Translate text between English and Arabic with dialect awareness. Generates cult
| Status | Type | When |
|--------|------|------|
-| 400 | `invalid_request_error` | Missing `text`, `source`, or `target`. Invalid values. Text too long. |
+| 400 | `invalid_request_error` | Missing `text` or `target`. Invalid values. Text over 5,000 characters. Source and target are the same language. |
| 401 | `authentication_error` | Invalid or missing API key |
| 402 | `insufficient_credits` | No credits remaining |
| 429 | `rate_limit_error` | Rate limit exceeded |
| 500 | `api_error` | Internal or provider error |
+
+
+ Source and target languages must be different. Setting both `source` and `target` to the same language (e.g. `"ar"` to `"ar"`) returns a 400 error.
+
diff --git a/api-reference/usage.mdx b/api-reference/usage.mdx
index d4b1b6b..3a5a142 100644
--- a/api-reference/usage.mdx
+++ b/api-reference/usage.mdx
@@ -1,11 +1,11 @@
---
title: "Usage"
sidebarTitle: "GET /v1/usage"
-description: "Retrieve API usage statistics, credit consumption, and request counts."
+description: "Retrieve API usage statistics, request counts, costs, and cache hit rates."
api: "GET https://api.trynawa.com/v1/usage"
---
-Retrieve your API usage statistics including request counts, credit consumption, and cache hit rates. This endpoint is **free**.
+Retrieve your API usage statistics including request counts, costs, and cache hit rates. This endpoint is **free**.
## Request
@@ -13,17 +13,49 @@ Retrieve your API usage statistics including request counts, credit consumption,
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
-| `from` | string | No | Start date (ISO 8601). Default: start of current month. |
-| `to` | string | No | End date (ISO 8601). Default: now. |
-| `group_by` | string | No | Group by: `day`, `week`, `month`, `endpoint`. Default: `day`. |
+| `from` | string | No | Start date (ISO 8601) |
+| `to` | string | No | End date (ISO 8601) |
+| `group_by` | string | No | Group by: `endpoint` (default) or `day`. |
### Example request
-```bash
-curl "https://api.trynawa.com/v1/usage?from=2025-01-01T00:00:00Z&group_by=endpoint" \
+
+
+```bash cURL
+curl "https://api.trynawa.com/v1/usage?from=2026-04-01&group_by=endpoint" \
-H "Authorization: Bearer nawa_test_sk_xxx"
```
+```typescript TypeScript
+import { Nawa } from '@nawalabs/sdk'
+
+const nawa = new Nawa({ apiKey: process.env.NAWA_API_KEY })
+
+const { data, error } = await nawa.usage({
+ from: '2026-04-01',
+ group_by: 'endpoint'
+})
+
+console.log(data.usage.total_requests)
+console.log(data.usage.cache_hit_rate)
+```
+
+```python Python
+from nawa import Nawa
+
+nawa = Nawa(api_key="your_api_key")
+
+result = nawa.usage(
+ from_date="2026-04-01",
+ group_by="endpoint"
+)
+
+print(result.data.usage.total_requests)
+print(result.data.usage.cache_hit_rate)
+```
+
+
+
## Response
### Success response (200)
@@ -32,44 +64,39 @@ curl "https://api.trynawa.com/v1/usage?from=2025-01-01T00:00:00Z&group_by=endpoi
{
"success": true,
"result": {
- "period": {
- "from": "2025-01-01T00:00:00Z",
- "to": "2025-01-31T23:59:59Z"
+ "usage": {
+ "total_requests": 1200,
+ "total_cost": 7.2,
+ "cache_hits": 280,
+ "cache_hit_rate": 0.23,
+ "period": {
+ "from": "2026-04-01",
+ "to": null
+ }
},
- "total_requests": 12450,
- "total_cost": 68.70,
- "cache_hit_rate": 0.23,
"by_endpoint": [
{
"endpoint": "/v1/classify",
- "requests": 8500,
- "cost": 51.00,
- "cache_hits": 1955,
- "avg_latency_ms": 820
+ "requests": 800,
+ "cost": 4.8,
+ "cache_hits": 200
},
{
- "endpoint": "/v1/rubric/classify",
- "requests": 2200,
- "cost": 6.60,
- "cache_hits": 880,
- "avg_latency_ms": 650
+ "endpoint": "/v1/translate",
+ "requests": 200,
+ "cost": 1.0,
+ "cache_hits": 50
},
{
- "endpoint": "/v1/comments/:id/reply",
- "requests": 1750,
- "cost": 14.00,
- "cache_hits": 0,
- "avg_latency_ms": 1200
+ "endpoint": "/v1/comments/reply",
+ "requests": 100,
+ "cost": 0.8,
+ "cache_hits": 0
}
- ],
- "balance": {
- "current": 31.30,
- "credits_purchased": 100.00,
- "credits_used": 68.70
- }
+ ]
},
"errors": [],
- "request_id": "req_usg_abc123"
+ "request_id": "req_nw_usg_abc123"
}
```
@@ -77,8 +104,46 @@ curl "https://api.trynawa.com/v1/usage?from=2025-01-01T00:00:00Z&group_by=endpoi
| Field | Type | Description |
|-------|------|-------------|
-| `total_requests` | integer | Total API requests in the period |
-| `total_cost` | number | Total credit cost in USD |
-| `cache_hit_rate` | number | Percentage of requests served from cache (0–1) |
-| `by_endpoint` | array | Breakdown by endpoint |
-| `balance` | object | Current balance and credit history |
+| `usage.total_requests` | integer | Total API requests in the period |
+| `usage.total_cost` | number | Total cost in USD |
+| `usage.cache_hits` | integer | Number of requests served from cache |
+| `usage.cache_hit_rate` | number | Ratio of cache hits to total requests (0-1) |
+| `usage.period` | object | Applied date filters (`from` and `to`, null if unset) |
+| `by_endpoint` | array | Breakdown per endpoint with `requests`, `cost`, and `cache_hits` |
+
+
+ When `group_by=day`, the breakdown key is `by_day` and each entry uses a `date` field (YYYY-MM-DD format) instead of `endpoint`.
+
+
+### Group by day example
+
+```bash
+curl "https://api.trynawa.com/v1/usage?group_by=day&from=2026-04-15" \
+ -H "Authorization: Bearer nawa_test_sk_xxx"
+```
+
+Response:
+```json
+{
+ "success": true,
+ "result": {
+ "usage": {
+ "total_requests": 350,
+ "total_cost": 2.1,
+ "cache_hits": 80,
+ "cache_hit_rate": 0.23,
+ "period": {"from": "2026-04-15", "to": null}
+ },
+ "by_day": [
+ {"date": "2026-04-20", "requests": 120, "cost": 0.72, "cache_hits": 30},
+ {"date": "2026-04-19", "requests": 110, "cost": 0.66, "cache_hits": 25},
+ {"date": "2026-04-18", "requests": 60, "cost": 0.36, "cache_hits": 15},
+ {"date": "2026-04-17", "requests": 35, "cost": 0.21, "cache_hits": 6},
+ {"date": "2026-04-16", "requests": 15, "cost": 0.09, "cache_hits": 3},
+ {"date": "2026-04-15", "requests": 10, "cost": 0.06, "cache_hits": 1}
+ ]
+ },
+ "errors": [],
+ "request_id": "req_nw_usg_day123"
+}
+```
diff --git a/openapi.yaml b/openapi.yaml
index 2c6201d..6458751 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -476,17 +476,26 @@ paths:
"500":
$ref: "#/components/responses/InternalError"
- /rubric/classify:
+ /rubric-classify:
post:
operationId: rubricClassify
summary: Classify with custom rubric
description: |
Classify text against a user-defined rubric. Define your own categories and
- scoring criteria for domain-specific classification.
+ scoring criteria for domain-specific classification. Supports both single-label
+ and multi-label classification with configurable confidence thresholds.
tags: [Classification]
x-nawa-cost: "$0.003"
- x-nawa-cache: true
+ x-nawa-cache: false
x-nawa-free: false
+ parameters:
+ - name: provider
+ in: query
+ required: false
+ schema:
+ type: string
+ enum: [claude, gemini, allam]
+ description: Force a specific AI provider for A/B testing.
requestBody:
required: true
content:
@@ -498,8 +507,7 @@ paths:
text:
type: string
minLength: 1
- maxLength: 5000
- description: The comment text to classify
+ description: The comment text to classify. Must be a non-empty string.
examples:
- "وين الترجمة العربية؟ مافهمت شي"
rubric:
@@ -511,27 +519,41 @@ paths:
minItems: 1
maxItems: 20
items:
- type: string
- description: List of category names to classify against
- descriptions:
- type: object
- additionalProperties:
- type: string
- description: Category descriptions to guide the model
- platform:
- type: string
- enum: [youtube, instagram, twitter, facebook]
- description: Source platform for context
+ type: object
+ required: [name]
+ properties:
+ name:
+ type: string
+ minLength: 1
+ description: Category name
+ description:
+ type: string
+ description: Category description to guide the model
+ description: Array of category objects. Each must have a non-empty `name`.
+ multi_label:
+ type: boolean
+ default: false
+ description: If `true`, returns all matching categories above the confidence threshold. Default is single-label (top match only).
+ confidence_threshold:
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 0.5
+ description: Minimum confidence score to include a category. Clamped to 0-1.
example:
text: "وين الترجمة العربية؟ مافهمت شي"
rubric:
- categories: ["translation_request", "technical_issue", "content_feedback", "off_topic"]
- descriptions:
- translation_request: "User is asking for subtitles or translation"
- technical_issue: "Audio, video, or playback problems"
- content_feedback: "Comments about the content quality"
- off_topic: "Not related to the video"
- platform: "youtube"
+ categories:
+ - name: "translation_request"
+ description: "User is asking for subtitles or translation"
+ - name: "technical_issue"
+ description: "Audio, video, or playback problems"
+ - name: "content_feedback"
+ description: "Comments about the content quality"
+ - name: "off_topic"
+ description: "Not related to the video"
+ multi_label: false
+ confidence_threshold: 0.5
responses:
"200":
description: Rubric classification result
@@ -560,21 +582,20 @@ paths:
value:
success: true
result:
- text: "وين الترجمة العربية؟ مافهمت شي"
- category: translation_request
- category_confidence: 0.94
- scores:
- translation_request: 0.94
- technical_issue: 0.03
- content_feedback: 0.02
- off_topic: 0.01
- dialect: gulf
- dialect_confidence: 0.91
+ id: rcl_nw_a1b2c3d4e5f6
+ object: rubric_classification
+ categories:
+ - name: translation_request
+ confidence: 0.94
+ multi_label: false
language: ar
- model: nagl-v1
- cached: false
+ provider: claude
+ model: claude-sonnet-4-5-20250929
+ fallback_used: false
+ cost_usd: 0.003
+ credits_used: 3
errors: []
- request_id: req_rub123abc456
+ request_id: req_nw_rub123abc456
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -759,37 +780,49 @@ paths:
"500":
$ref: "#/components/responses/InternalError"
- /comments/{id}/reply:
+ /comments/reply:
post:
operationId: generateReply
- summary: Generate reply to comment
+ summary: Classify and reply to a comment
description: |
- Generate a culturally-aware, dialect-matched reply to an Arabic comment.
- Replies match the commenter's dialect and are contextually appropriate.
+ Classify a comment and generate a culturally-aware, dialect-matched reply in
+ a single call. For Arabic comments, replies match the detected dialect (Gulf,
+ Egyptian, Levantine, MSA). For English comments, replies are natural and
+ platform-appropriate.
+
+ The response includes both the classification result (intent, sentiment,
+ priority) and the generated reply text.
tags: [Reply]
x-nawa-cost: "$0.008"
- x-nawa-cache: true
+ x-nawa-cache: false
x-nawa-free: false
parameters:
- - name: id
- in: path
- required: true
- description: The comment ID to reply to
+ - name: provider
+ in: query
+ required: false
schema:
type: string
- examples:
- - "cmt_abc123"
+ enum: [claude, gemini, allam]
+ description: Force a specific AI provider for A/B testing. Default is NAGL's language-based routing.
requestBody:
- required: false
+ required: true
content:
application/json:
schema:
type: object
+ required: [text]
properties:
+ text:
+ type: string
+ minLength: 1
+ description: The comment text to classify and reply to. Must be a non-empty string.
+ examples:
+ - "متى الجزء الثاني؟"
+ - "Great video, but the audio quality could be better"
tone:
type: string
enum: [friendly, professional, casual, formal]
- default: friendly
+ default: professional
description: Reply tone
max_length:
type: integer
@@ -798,28 +831,31 @@ paths:
default: 500
description: Maximum reply length in characters
context:
- type: string
- description: Additional context about the channel or video
- language:
- type: string
- enum: [ar, en, auto]
- default: auto
- description: "Force reply language. Default: auto (matches commenter's language)."
+ type: object
+ properties:
+ platform:
+ type: string
+ description: Source platform (e.g. youtube, instagram)
+ brand_voice:
+ type: string
+ description: Brand voice description for reply style
+ description: Optional context to improve reply relevance
example:
- tone: "friendly"
- context: "Tech review channel focused on smartphones"
+ text: "متى الجزء الثاني؟"
+ tone: "professional"
+ context:
+ platform: "youtube"
+ brand_voice: "Tech review channel focused on smartphones"
responses:
"200":
- description: Generated reply
+ description: Classification and generated reply
headers:
X-Request-Id:
$ref: "#/components/headers/X-Request-Id"
X-NAWA-Provider:
$ref: "#/components/headers/X-NAWA-Provider"
- X-NAWA-Cache:
- $ref: "#/components/headers/X-NAWA-Cache"
- X-NAWA-Balance:
- $ref: "#/components/headers/X-NAWA-Balance"
+ X-NAWA-Latency:
+ $ref: "#/components/headers/X-NAWA-Latency"
X-RateLimit-Limit:
$ref: "#/components/headers/X-RateLimit-Limit"
X-RateLimit-Remaining:
@@ -832,18 +868,27 @@ paths:
$ref: "#/components/schemas/ReplySuccessResponse"
examples:
gulf_reply:
- summary: Friendly reply to Gulf Arabic question
+ summary: Reply to Gulf Arabic question
value:
success: true
result:
- comment_id: cmt_abc123
- reply_text: "إن شاء الله الجزء الثاني قريب! تابعنا عشان ما يفوتك 🔔"
- reply_dialect: gulf
- tone: friendly
- original_intent: question
- original_dialect: gulf
+ id: rpl_nw_a1b2c3d4e5f6
+ object: comment_reply
+ classification:
+ intent: ["question"]
+ sentiment: neutral
+ priority: medium
+ requires_response: true
+ reply:
+ text: "إن شاء الله الجزء الثاني قريب! تابعنا عشان ما يفوتك"
+ direction: rtl
+ tone: professional
+ provider: claude
+ model: claude-sonnet-4-5-20250929
+ cost_usd: 0.008
+ credits_used: 8
errors: []
- request_id: req_rep789xyz012
+ request_id: req_nw_rep789xyz012
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -930,18 +975,14 @@ paths:
operationId: listComments
summary: List classified comments
description: |
- Retrieve classified comments with filtering and pagination. Free endpoint.
+ Retrieve previously classified comments with filtering and offset-based
+ pagination. Returns comments stored from prior `/v1/classify` calls.
+ Free endpoint.
tags: [Comments]
x-nawa-cost: "Free"
x-nawa-cache: false
x-nawa-free: true
parameters:
- - name: channel_id
- in: query
- required: false
- schema:
- type: string
- description: Filter by channel ID
- name: platform
in: query
required: false
@@ -970,27 +1011,6 @@ paths:
type: string
enum: [gulf, egyptian, levantine, msa]
description: Filter by dialect
- - name: toxicity
- in: query
- required: false
- schema:
- type: string
- enum: [none, mild, moderate, severe]
- description: Filter by toxicity
- - name: from
- in: query
- required: false
- schema:
- type: string
- format: date-time
- description: Start date (ISO 8601)
- - name: to
- in: query
- required: false
- schema:
- type: string
- format: date-time
- description: End date (ISO 8601)
- name: limit
in: query
required: false
@@ -999,13 +1019,15 @@ paths:
minimum: 1
maximum: 100
default: 25
- description: Results per page
- - name: cursor
+ description: Results per page (1-100)
+ - name: offset
in: query
required: false
schema:
- type: string
- description: Pagination cursor from previous response
+ type: integer
+ minimum: 0
+ default: 0
+ description: Number of results to skip for pagination
responses:
"200":
description: List of classified comments
@@ -1017,27 +1039,27 @@ paths:
schema:
$ref: "#/components/schemas/CommentsListSuccessResponse"
examples:
- youtube_questions:
- summary: YouTube questions
+ filtered:
+ summary: Filtered comments with pagination
value:
success: true
result:
comments:
- - id: cmt_abc123
+ - id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
+ request_id: "req_nw_abc123"
text: "متى الجزء الثاني؟"
platform: youtube
intent: question
sentiment: neutral
dialect: gulf
- toxicity: none
- created_at: "2025-01-15T10:30:00Z"
- channel_id: ch_123
+ created_at: "2026-04-20T10:30:00Z"
pagination:
+ total: 150
+ limit: 25
+ offset: 0
has_more: true
- next_cursor: cur_def456
- total_count: 1523
errors: []
- request_id: req_lst_abc123
+ request_id: req_nw_lst_abc123
"401":
$ref: "#/components/responses/Unauthorized"
"429":
@@ -1048,28 +1070,15 @@ paths:
/analytics:
get:
operationId: getAnalytics
- summary: Get analytics
+ summary: Get API analytics
description: |
- Retrieve aggregated analytics on comment trends, sentiment shifts, and
- engagement patterns. Free endpoint.
+ Retrieve aggregated API usage analytics including request counts, costs,
+ cache hit rates, latency, and endpoint/provider distribution. Free endpoint.
tags: [Analytics]
x-nawa-cost: "Free"
x-nawa-cache: false
x-nawa-free: true
parameters:
- - name: channel_id
- in: query
- required: false
- schema:
- type: string
- description: Filter by channel ID
- - name: platform
- in: query
- required: false
- schema:
- type: string
- enum: [youtube, instagram, twitter, facebook]
- description: Filter by platform
- name: from
in: query
required: false
@@ -1089,9 +1098,9 @@ paths:
required: false
schema:
type: string
- enum: [day, week, month]
- default: day
- description: Group results by time period
+ enum: [endpoint, day, provider]
+ default: endpoint
+ description: Group the breakdown by endpoint name, calendar day, or AI provider
responses:
"200":
description: Analytics data
@@ -1103,45 +1112,42 @@ paths:
schema:
$ref: "#/components/schemas/AnalyticsSuccessResponse"
examples:
- monthly:
- summary: Monthly YouTube analytics
+ by_endpoint:
+ summary: Analytics grouped by endpoint
value:
success: true
result:
- period:
- from: "2025-01-01T00:00:00Z"
- to: "2025-01-31T23:59:59Z"
- total_comments: 4521
- summary:
- intent:
- question: 1205
- praise: 1890
- complaint: 678
- suggestion: 412
- spam: 198
- other: 138
- sentiment:
- positive: 2100
- negative: 890
- neutral: 1231
- mixed: 300
- dialect:
- gulf: 2015
- egyptian: 1302
- levantine: 789
- msa: 415
- toxicity:
- none: 3950
- mild: 320
- moderate: 180
- severe: 71
- trends:
- - period: "2025-01-06/2025-01-12"
- total: 1123
- sentiment_score: 0.72
- top_intent: praise
+ analytics:
+ total_requests: 1234
+ total_cost: 7.404
+ cache_hits: 500
+ cache_hit_rate: 0.41
+ avg_latency_ms: 230
+ success_rate: 0.98
+ error_count: 25
+ endpoint_distribution:
+ "/v1/classify": 800
+ "/v1/translate": 200
+ "/v1/detect": 150
+ "/v1/moderate": 84
+ provider_distribution:
+ allam: 600
+ claude: 400
+ gemini: 234
+ period:
+ from: null
+ to: null
+ by_endpoint:
+ - endpoint: "/v1/classify"
+ requests: 800
+ cost: 4.8
+ avg_latency_ms: 200
+ - endpoint: "/v1/translate"
+ requests: 200
+ cost: 1.0
+ avg_latency_ms: 350
errors: []
- request_id: req_ana_abc123
+ request_id: req_nw_ana_abc123
"401":
$ref: "#/components/responses/Unauthorized"
"429":
@@ -1155,7 +1161,8 @@ paths:
summary: Health check
description: |
Check the operational status of the NAWA API and its dependencies.
- No authentication required.
+ No authentication required. Returns service-level status for database,
+ Redis cache, and the NAGL classification engine.
tags: [System]
x-nawa-cost: "Free"
x-nawa-cache: false
@@ -1172,18 +1179,19 @@ paths:
healthy:
summary: All services operational
value:
- success: true
- result:
- status: healthy
- version: "1.0.0"
- services:
- api: operational
- classification: operational
- database: operational
- cache: operational
- timestamp: "2025-01-15T12:00:00Z"
- errors: []
- request_id: req_hlt_abc123
+ status: healthy
+ version: v1
+ services:
+ database:
+ status: healthy
+ latency_ms: 12
+ redis:
+ status: healthy
+ latency_ms: 3
+ nagl:
+ status: healthy
+ timestamp: "2026-04-20T12:00:00.000Z"
+ latency_ms: 45
"503":
description: One or more services degraded
content:
@@ -1209,22 +1217,22 @@ paths:
schema:
type: string
format: date-time
- description: Start date (ISO 8601). Default: start of current month.
+ description: Start date (ISO 8601)
- name: to
in: query
required: false
schema:
type: string
format: date-time
- description: End date (ISO 8601). Default: now.
+ description: End date (ISO 8601)
- name: group_by
in: query
required: false
schema:
type: string
- enum: [day, week, month, endpoint]
- default: day
- description: Group results by time period or endpoint
+ enum: [endpoint, day]
+ default: endpoint
+ description: Group results by endpoint name or calendar day
responses:
"200":
description: Usage statistics
@@ -1241,34 +1249,29 @@ paths:
value:
success: true
result:
- period:
- from: "2025-01-01T00:00:00Z"
- to: "2025-01-31T23:59:59Z"
- total_requests: 12450
- total_cost: 68.70
- cache_hit_rate: 0.23
+ usage:
+ total_requests: 1200
+ total_cost: 7.2
+ cache_hits: 280
+ cache_hit_rate: 0.23
+ period:
+ from: null
+ to: null
by_endpoint:
- endpoint: "/v1/classify"
- requests: 8500
- cost: 51.00
- cache_hits: 1955
- avg_latency_ms: 820
+ requests: 800
+ cost: 4.8
+ cache_hits: 200
- endpoint: "/v1/translate"
- requests: 1200
- cost: 6.00
- cache_hits: 276
- avg_latency_ms: 1100
- - endpoint: "/v1/comments/:id/reply"
- requests: 1750
- cost: 14.00
+ requests: 200
+ cost: 1.0
+ cache_hits: 50
+ - endpoint: "/v1/comments/reply"
+ requests: 100
+ cost: 0.8
cache_hits: 0
- avg_latency_ms: 1200
- balance:
- current: 31.30
- credits_purchased: 100.00
- credits_used: 68.70
errors: []
- request_id: req_usg_abc123
+ request_id: req_nw_usg_abc123
"401":
$ref: "#/components/responses/Unauthorized"
"429":
@@ -1280,7 +1283,10 @@ paths:
post:
operationId: createWebhook
summary: Create webhook endpoint
- description: Register a new webhook endpoint for event notifications.
+ description: |
+ Register a new webhook endpoint for event notifications. Maximum 10 active
+ endpoints per account. The `signing_secret` is returned only once at
+ creation -- store it securely for verifying webhook signatures.
tags: [Webhooks]
x-nawa-cost: "Free"
x-nawa-cache: false
@@ -1296,7 +1302,7 @@ paths:
url:
type: string
format: uri
- description: HTTPS endpoint URL
+ description: HTTPS endpoint URL (must use HTTPS)
examples:
- "https://example.com/webhooks/nawa"
events:
@@ -1307,8 +1313,6 @@ paths:
enum:
- classification.completed
- classification.failed
- - comment.new
- - comment.replied
- credits.low
- credits.exhausted
responses:
@@ -1943,62 +1947,109 @@ components:
RubricResult:
type: object
+ required: [id, object, categories, multi_label, language, provider, model, fallback_used, cost_usd, credits_used]
properties:
- text:
- type: string
- description: The original input text
- category:
+ id:
type: string
- description: The top matching category from the rubric
- category_confidence:
- type: number
- minimum: 0
- maximum: 1
- scores:
- type: object
- additionalProperties:
- type: number
- description: Confidence scores for all rubric categories
- dialect:
+ description: Rubric classification ID in `rcl_nw_xxx` format
+ examples:
+ - "rcl_nw_a1b2c3d4e5f6"
+ object:
type: string
- enum: [gulf, egyptian, levantine, msa]
- dialect_confidence:
- type: number
- minimum: 0
- maximum: 1
+ const: rubric_classification
+ categories:
+ type: array
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ description: Category name from the rubric
+ confidence:
+ type: number
+ minimum: 0
+ maximum: 1
+ description: Confidence score for this category
+ description: Matched categories above the confidence threshold
+ multi_label:
+ type: boolean
+ description: Whether multi-label mode was used
language:
type: string
enum: [ar, en, mixed]
+ description: Detected language of the input text
+ provider:
+ type: string
+ enum: [claude, allam, gemini]
+ description: AI provider used
model:
type: string
- cached:
+ description: Specific model ID
+ fallback_used:
type: boolean
+ description: Whether a fallback provider was used
+ cost_usd:
+ type: number
+ description: Always `0.003` for this endpoint
+ credits_used:
+ type: integer
+ description: Always `3` for this endpoint
ReplyResult:
type: object
+ required: [id, object, classification, reply, provider, model, cost_usd, credits_used]
properties:
- comment_id:
- type: string
- description: The comment that was replied to
- reply_text:
- type: string
- description: The generated reply text
- reply_dialect:
+ id:
type: string
- enum: [gulf, egyptian, levantine, msa]
- description: Dialect used in the reply
- tone:
+ description: Reply ID in `rpl_nw_xxx` format
+ examples:
+ - "rpl_nw_a1b2c3d4e5f6"
+ object:
type: string
- enum: [friendly, professional, casual, formal]
- description: The tone used for the reply
- original_intent:
+ const: comment_reply
+ classification:
+ type: object
+ description: Classification results for the input comment
+ properties:
+ intent:
+ type: array
+ items:
+ type: string
+ description: Detected intent labels
+ sentiment:
+ type: string
+ enum: [positive, negative, neutral, mixed]
+ priority:
+ type: string
+ enum: [low, medium, high]
+ requires_response:
+ type: boolean
+ reply:
+ type: object
+ description: Generated reply
+ properties:
+ text:
+ type: string
+ description: The generated reply text
+ direction:
+ type: string
+ enum: [rtl, ltr]
+ description: Script direction for UI rendering
+ tone:
+ type: string
+ enum: [friendly, professional, casual, formal]
+ provider:
type: string
- enum: [question, complaint, praise, suggestion, spam, other]
- description: Detected intent of the original comment
- original_dialect:
+ description: AI provider used
+ model:
type: string
- enum: [gulf, egyptian, levantine, msa]
- description: Detected dialect of the original comment
+ description: Specific model ID
+ cost_usd:
+ type: number
+ description: Always `0.008` for this endpoint
+ credits_used:
+ type: integer
+ description: Always `8` for this endpoint
FeedbackInput:
type: object
@@ -2059,6 +2110,11 @@ components:
properties:
id:
type: string
+ format: uuid
+ description: Database record ID
+ request_id:
+ type: string
+ description: Original request ID from the classify call
text:
type: string
platform:
@@ -2073,72 +2129,92 @@ components:
dialect:
type: string
enum: [gulf, egyptian, levantine, msa]
- toxicity:
- type: string
- enum: [none, mild, moderate, severe]
created_at:
type: string
format: date-time
- channel_id:
- type: string
Pagination:
type: object
properties:
+ total:
+ type: integer
+ description: Total number of matching comments
+ limit:
+ type: integer
+ description: Current page size
+ offset:
+ type: integer
+ description: Current offset
has_more:
type: boolean
- next_cursor:
- type: string
- nullable: true
- total_count:
- type: integer
+ description: Whether more results exist beyond the current page
AnalyticsResult:
type: object
properties:
- period:
+ analytics:
type: object
properties:
- from:
- type: string
- format: date-time
- to:
- type: string
- format: date-time
- total_comments:
- type: integer
- summary:
- type: object
- properties:
- intent:
- type: object
- additionalProperties:
- type: integer
- sentiment:
+ total_requests:
+ type: integer
+ description: Total API requests in the period
+ total_cost:
+ type: number
+ description: Total cost in USD
+ cache_hits:
+ type: integer
+ description: Number of requests served from cache
+ cache_hit_rate:
+ type: number
+ minimum: 0
+ maximum: 1
+ description: Ratio of cache hits to total requests
+ avg_latency_ms:
+ type: integer
+ description: Average response latency in milliseconds
+ success_rate:
+ type: number
+ minimum: 0
+ maximum: 1
+ description: Ratio of successful (2xx) responses
+ error_count:
+ type: integer
+ description: Number of error (4xx/5xx) responses
+ endpoint_distribution:
type: object
additionalProperties:
type: integer
- dialect:
+ description: Request counts per endpoint
+ provider_distribution:
type: object
additionalProperties:
type: integer
- toxicity:
+ description: Request counts per AI provider
+ period:
type: object
- additionalProperties:
- type: integer
- trends:
+ properties:
+ from:
+ type: string
+ format: date-time
+ nullable: true
+ to:
+ type: string
+ format: date-time
+ nullable: true
+ by_endpoint:
type: array
+ description: Breakdown grouped by the `group_by` query parameter. The key name changes to match the grouping (e.g. `by_day`, `by_provider`).
items:
type: object
properties:
- period:
+ endpoint:
type: string
- total:
+ requests:
type: integer
- sentiment_score:
+ cost:
type: number
- top_intent:
- type: string
+ avg_latency_ms:
+ type: integer
HealthResult:
type: object
@@ -2146,72 +2222,94 @@ components:
status:
type: string
enum: [healthy, degraded]
+ description: Overall API status. `healthy` when all services are healthy; `degraded` when any service is degraded or down.
version:
type: string
+ description: API version (always `v1`)
services:
type: object
properties:
- api:
- type: string
- enum: [operational, degraded, down]
- classification:
- type: string
- enum: [operational, degraded, down]
database:
- type: string
- enum: [operational, degraded, down]
- cache:
- type: string
- enum: [operational, degraded, down]
+ type: object
+ properties:
+ status:
+ type: string
+ enum: [healthy, degraded, down]
+ latency_ms:
+ type: integer
+ description: Database query latency in milliseconds
+ redis:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: [healthy, degraded, down]
+ latency_ms:
+ type: integer
+ description: Redis ping latency in milliseconds
+ nagl:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: [healthy, down]
+ description: Whether the NAGL classification engine is enabled
timestamp:
type: string
format: date-time
+ latency_ms:
+ type: integer
+ description: Total health check latency in milliseconds
UsageResult:
type: object
properties:
- period:
+ usage:
type: object
properties:
- from:
- type: string
- format: date-time
- to:
- type: string
- format: date-time
- total_requests:
- type: integer
- total_cost:
- type: number
- description: Total cost in USD
- cache_hit_rate:
- type: number
- minimum: 0
- maximum: 1
+ total_requests:
+ type: integer
+ description: Total API requests in the period
+ total_cost:
+ type: number
+ description: Total cost in USD
+ cache_hits:
+ type: integer
+ description: Number of requests served from cache
+ cache_hit_rate:
+ type: number
+ minimum: 0
+ maximum: 1
+ description: Ratio of cache hits to total requests
+ period:
+ type: object
+ properties:
+ from:
+ type: string
+ format: date-time
+ nullable: true
+ to:
+ type: string
+ format: date-time
+ nullable: true
by_endpoint:
type: array
+ description: Breakdown grouped by the `group_by` query parameter. When `group_by=day`, the key is `date` instead of `endpoint`.
items:
type: object
properties:
endpoint:
type: string
+ description: Endpoint path (when `group_by=endpoint`)
+ date:
+ type: string
+ description: Calendar date YYYY-MM-DD (when `group_by=day`)
requests:
type: integer
cost:
type: number
cache_hits:
type: integer
- avg_latency_ms:
- type: integer
- balance:
- type: object
- properties:
- current:
- type: number
- credits_purchased:
- type: number
- credits_used:
- type: number
WebhookEndpoint:
type: object
diff --git a/webhooks.mdx b/webhooks.mdx
index 2ef4922..c52014d 100644
--- a/webhooks.mdx
+++ b/webhooks.mdx
@@ -12,11 +12,13 @@ Receive real-time notifications when events occur in your NAWA account. Webhooks
|-------|-------------|---------|
| `classification.completed` | A classification request succeeded | After `/v1/classify` completes |
| `classification.failed` | A classification request failed | On provider or internal errors |
-| `comment.new` | A new comment was ingested | When a connected platform receives a comment |
-| `comment.replied` | A reply was posted | After `/v1/comments/:id/reply` succeeds |
| `credits.low` | Credit balance below threshold | Balance drops below $5 |
| `credits.exhausted` | Credit balance is $0 | Balance reaches $0 |
+
+ You can register up to **10 active webhook endpoints** per account.
+
+
## Webhook payload
```json