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