diff --git a/api-reference/comments-reply.mdx b/api-reference/comments-reply.mdx index d6321d7..ebb9c98 100644 --- a/api-reference/comments-reply.mdx +++ b/api-reference/comments-reply.mdx @@ -1,60 +1,80 @@ --- -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: "Classify and reply" +sidebarTitle: "POST /v1/comments/reply" +description: "Classify a comment and generate a contextual reply in a single API 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 a context-aware 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. - Cost: **$0.008** per request (8 credits). Semantic cache hits are free (`X-NAWA-Cache: HIT`). + Cost: **$0.008** per request (8 credits). ## 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` | + +### Query parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `id` | string | Yes | The comment ID to reply to. | +| `provider` | string | No | Force a specific AI provider for A/B testing: `claude`, `gemini`, `allam`. | ### Body 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). | +| `text` | string | Yes | The comment text to classify and reply to. | +| `tone` | string | No | Reply tone: `friendly`, `professional`, `casual`, `formal`. Default: `professional`. | +| `max_length` | integer | No | Maximum reply length in characters (1-2000). Default: 500. | ### 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" + "max_length": 500 }' ``` ```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' + maxLength: 500 }) + +console.log(data.reply.text) ``` ```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" + max_length=500 ) + +print(result.data.reply["text"]) ``` @@ -67,25 +87,144 @@ 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": "allam", + "model": "sdaia/allam-1-13b-instruct", + "cost_usd": 0.008, + "credits_used": 8 }, "errors": [], "request_id": "req_rep789xyz012" } ``` +### Response headers + +| Header | Description | +|--------|-------------| +| `X-Request-Id` | Unique request identifier for debugging | +| `X-NAWA-Provider` | AI provider used for this request | +| `X-RateLimit-Limit` | Rate limit ceiling for current window | +| `X-RateLimit-Remaining` | Requests remaining in current window | +| `X-RateLimit-Reset` | Window reset time (RFC 3339) | +| `X-NAWA-Balance` | Current credit balance in USD | + ### Result fields | 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 (`rpl_nw_xxx` format) | +| `object` | string | Always `comment_reply` | +| `classification` | object | Classification of the input comment | +| `classification.intent` | string[] | Detected intents | +| `classification.sentiment` | string | `positive`, `negative`, `neutral`, `mixed` | +| `classification.priority` | string | `high`, `medium`, `low` | +| `classification.requires_response` | boolean | Whether the comment warrants a reply | +| `reply` | object | Generated reply | +| `reply.text` | string | The generated reply text | +| `reply.direction` | string | Text direction: `ltr` or `rtl` | +| `reply.tone` | string | The tone used for the reply | +| `provider` | string | AI provider used | +| `model` | string | Model used for generation | +| `cost_usd` | number | Cost of this request in USD | +| `credits_used` | integer | Credits deducted | + +### Error responses + +| Status | Type | When | +|--------|------|------| +| 400 | `invalid_request_error` | Missing `text`, invalid `tone`, `max_length` out of range | +| 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 | + +### 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 is terrible in this video", "tone": "professional"}' + ``` + + Response: + ```json + { + "success": true, + "result": { + "id": "rpl_nw_b2c3d4e5f6g7", + "object": "comment_reply", + "classification": { + "intent": ["complaint"], + "sentiment": "negative", + "priority": "high", + "requires_response": true + }, + "reply": { + "text": "Thank you for the feedback! We're aware of the audio issue and are working on improving it for future videos.", + "direction": "ltr", + "tone": "professional" + }, + "provider": "claude", + "model": "claude-haiku-4-5-20251001", + "cost_usd": 0.008, + "credits_used": 8 + }, + "errors": [], + "request_id": "req_en_reply_001" + } + ``` + + + + ```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": "casual"}' + ``` + + Response: + ```json + { + "success": true, + "result": { + "id": "rpl_nw_c3d4e5f6g7h8", + "object": "comment_reply", + "classification": { + "intent": ["complaint"], + "sentiment": "negative", + "priority": "high", + "requires_response": true + }, + "reply": { + "text": "نأسف على تجربتك! نحب نعرف وش بالضبط ما عجبك عشان نحسنه", + "direction": "rtl", + "tone": "casual" + }, + "provider": "allam", + "model": "sdaia/allam-1-13b-instruct", + "cost_usd": 0.008, + "credits_used": 8 + }, + "errors": [], + "request_id": "req_ar_reply_001" + } + ``` + + diff --git a/api-reference/feedback.mdx b/api-reference/feedback.mdx index 4cddf8b..2d2723c 100644 --- a/api-reference/feedback.mdx +++ b/api-reference/feedback.mdx @@ -1,14 +1,14 @@ --- -title: "Submit Feedback" +title: "Submit feedback" sidebarTitle: "POST /v1/feedback" -description: "Submit RLHF feedback to improve classification accuracy over time." +description: "Submit RLHF feedback on a classification result to improve accuracy over time." api: "POST https://api.trynawa.com/v1/feedback" --- -Submit reinforcement learning from human feedback (RLHF) to continuously improve NAWA's classification accuracy. +Submit feedback on a classification result to improve NAWA's accuracy through reinforcement learning from human feedback (RLHF). This endpoint is free for all tiers. - This endpoint is **free** -- 0 credits, no cost. + This endpoint is **free** - 0 credits, no cost. @@ -17,14 +17,22 @@ Submit reinforcement learning from human feedback (RLHF) to continuously improve ## 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 | |-----------|------|----------|-------------| -| `request_id` | string | Yes | The `request_id` from the original classification response. | -| `field` | string | Yes | The field to correct: `intent`, `sentiment`, `dialect`, `toxicity`, `category`. | -| `expected_value` | string | Yes | The correct value the model should have returned. | -| `comment` | string | No | Optional free-text explanation of why this correction is needed. | +| `classification_id` | string | Yes | The classification ID (`cls_nw_xxx`) from a `/v1/classify` response. | +| `rating` | string | Yes | `correct`, `incorrect`, or `partial`. | +| `corrected_intent` | string[] | No | The correct intent(s) if the classification was wrong. | +| `corrected_sentiment` | string | No | The correct sentiment: `positive`, `negative`, `neutral`, `mixed`. | +| `comment` | string | No | Free-text explanation of why this correction is needed. | ### Example request @@ -35,28 +43,39 @@ curl -X POST https://api.trynawa.com/v1/feedback \ -H "Authorization: Bearer nawa_test_sk_xxx" \ -H "Content-Type: application/json" \ -d '{ - "request_id": "req_abc123def456", - "field": "dialect", - "expected_value": "levantine", - "comment": "This is Lebanese Arabic, not Gulf" + "classification_id": "cls_nw_a1b2c3d4e5f6", + "rating": "incorrect", + "corrected_intent": ["question"], + "corrected_sentiment": "neutral", + "comment": "This is a question about availability, not praise" }' ``` ```typescript TypeScript +import { Nawa } from '@nawalabs/sdk' + +const nawa = new Nawa({ apiKey: process.env.NAWA_API_KEY }) + const { data, error } = await nawa.feedback.submit({ - requestId: 'req_abc123def456', - field: 'dialect', - expectedValue: 'levantine', - comment: 'This is Lebanese Arabic, not Gulf' + classificationId: 'cls_nw_a1b2c3d4e5f6', + rating: 'incorrect', + correctedIntent: ['question'], + correctedSentiment: 'neutral', + comment: 'This is a question about availability, not praise' }) ``` ```python Python +from nawa import Nawa + +nawa = Nawa(api_key="your_api_key") + result = nawa.feedback.submit( - request_id="req_abc123def456", - field="dialect", - expected_value="levantine", - comment="This is Lebanese Arabic, not Gulf" + classification_id="cls_nw_a1b2c3d4e5f6", + rating="incorrect", + corrected_intent=["question"], + corrected_sentiment="neutral", + comment="This is a question about availability, not praise" ) ``` @@ -70,11 +89,32 @@ result = nawa.feedback.submit( { "success": true, "result": { - "feedback_id": "fb_xyz789", - "status": "accepted", - "message": "Thank you! Your feedback helps improve NAWA's accuracy." + "id": "fb_nw_a1b2c3d4e5f6", + "object": "feedback", + "classification_id": "cls_nw_a1b2c3d4e5f6", + "rating": "incorrect", + "acknowledged": true }, "errors": [], "request_id": "req_fb_abc123" } ``` + +### Result fields + +| Field | Type | Description | +|-------|------|-------------| +| `id` | string | Feedback ID (`fb_nw_xxx` format) | +| `object` | string | Always `feedback` | +| `classification_id` | string | The classification that was corrected | +| `rating` | string | The rating you submitted | +| `acknowledged` | boolean | Always `true` on success | + +### Error responses + +| Status | Type | When | +|--------|------|------| +| 400 | `invalid_request_error` | Missing `classification_id`, invalid `rating` | +| 401 | `authentication_error` | Invalid or missing API key | +| 429 | `rate_limit_error` | Rate limit exceeded | +| 500 | `api_error` | Internal error | diff --git a/api-reference/rubric-classify.mdx b/api-reference/rubric-classify.mdx index 4489175..3db26b8 100644 --- a/api-reference/rubric-classify.mdx +++ b/api-reference/rubric-classify.mdx @@ -1,27 +1,43 @@ --- -title: "Rubric Classify" +title: "Rubric classify" sidebarTitle: "POST /v1/rubric/classify" -description: "Classify a comment against a custom rubric with predefined scoring criteria." +description: "Classify text against a custom rubric with user-defined categories and scoring criteria." 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, descriptions, and confidence thresholds for domain-specific classification. Supports single-label and multi-label modes. - 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` | + +### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `provider` | string | No | Force a specific AI provider for A/B testing: `claude`, `gemini`, `allam`. | + ### 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. Max 5,000 characters. | +| `rubric` | object | Yes | The rubric definition. | +| `rubric.categories` | object[] | Yes | Array of category objects. Min 1, max 20. | +| `rubric.categories[].name` | string | Yes | Category name. | +| `rubric.categories[].description` | string | No | Description to guide the model. | +| `rubric.multi_label` | boolean | No | If `true`, return all categories above the threshold. Default: `false`. | +| `rubric.confidence_threshold` | number | No | Minimum confidence to include a category (0-1). Default: `0.5`. | +| `platform` | string | No | Source platform: `youtube`, `instagram`, `twitter`, `facebook`. | ### Example request @@ -34,48 +50,63 @@ curl -X POST https://api.trynawa.com/v1/rubric/classify \ -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" - } + "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" }' ``` ```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' - } + 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' } + ], + multiLabel: false, + confidenceThreshold: 0.5 }, platform: 'youtube' }) + +console.log(data.categories) ``` ```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", ) + +print(result.data.categories) ``` @@ -88,34 +119,106 @@ 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": "allam", + "model": "sdaia/allam-1-13b-instruct", + "fallback_used": false, + "cost_usd": 0.003, + "credits_used": 3 }, "errors": [], "request_id": "req_rub123abc456" } ``` +### Response headers + +| Header | Description | +|--------|-------------| +| `X-Request-Id` | Unique request identifier for debugging | +| `X-NAWA-Provider` | AI provider used for this request | +| `X-RateLimit-Limit` | Rate limit ceiling for current window | +| `X-RateLimit-Remaining` | Requests remaining in current window | +| `X-RateLimit-Reset` | Window reset time (RFC 3339) | +| `X-NAWA-Balance` | Current credit balance in USD | + ### Result fields | 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 | Classification ID (`rcl_nw_xxx` format) | +| `object` | string | Always `rubric_classification` | +| `categories` | object[] | Matched categories with confidence scores | +| `categories[].name` | string | Category name | +| `categories[].confidence` | number | Confidence score (0-1) | +| `multi_label` | boolean | Whether multi-label mode was used | +| `language` | string | Detected language: `ar`, `en`, `mixed` | +| `provider` | string | AI provider used | +| `model` | string | Model used for classification | +| `fallback_used` | boolean | Whether a fallback provider was used | +| `cost_usd` | number | Cost of this request in USD | +| `credits_used` | integer | Credits deducted | + +### Error responses + +| Status | Type | When | +|--------|------|------| +| 400 | `invalid_request_error` | Missing `text` or `rubric`, too many categories (max 20), empty category name | +| 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 | + +### More examples + + + + ```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": "الصوت سيء والترجمة مو موجودة", + "rubric": { + "categories": [ + {"name": "translation_request"}, + {"name": "technical_issue"}, + {"name": "content_feedback"} + ], + "multi_label": true, + "confidence_threshold": 0.3 + } + }' + ``` + + Response: + ```json + { + "success": true, + "result": { + "id": "rcl_nw_d4e5f6g7h8i9", + "object": "rubric_classification", + "categories": [ + {"name": "technical_issue", "confidence": 0.88}, + {"name": "translation_request", "confidence": 0.72} + ], + "multi_label": true, + "language": "ar", + "provider": "allam", + "model": "sdaia/allam-1-13b-instruct", + "fallback_used": false, + "cost_usd": 0.003, + "credits_used": 3 + }, + "errors": [], + "request_id": "req_rub_multi_001" + } + ``` + + diff --git a/api-reference/webhooks-manage.mdx b/api-reference/webhooks-manage.mdx new file mode 100644 index 0000000..fbdbcd9 --- /dev/null +++ b/api-reference/webhooks-manage.mdx @@ -0,0 +1,210 @@ +--- +title: "Webhook management" +sidebarTitle: "Webhooks" +description: "Create, list, and deactivate webhook endpoints for real-time event notifications." +--- + +Manage webhook endpoints for your NAWA account. You can register up to 10 active endpoints per account. + + + Webhook management is **free** - 0 credits, no cost. + + +## Create a webhook endpoint + +`POST /v1/webhooks` + +Register a new webhook endpoint for event notifications. The signing secret is returned only once at creation. + +### Body parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `url` | string | Yes | HTTPS endpoint URL. Must use `https://`. | +| `events` | string[] | Yes | Event types to subscribe to. | + +### Supported events + +| Event | Description | +|-------|-------------| +| `classification.completed` | A classification request succeeded | +| `classification.failed` | A classification request failed | +| `credits.low` | Credit balance dropped below $5 | +| `credits.exhausted` | Credit balance reached $0 | + +### Example request + + + +```bash cURL +curl -X POST https://api.trynawa.com/v1/webhooks \ + -H "Authorization: Bearer nawa_live_sk_xxx" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com/webhooks/nawa", + "events": ["classification.completed", "credits.low"] + }' +``` + +```typescript TypeScript +import { Nawa } from '@nawalabs/sdk' + +const nawa = new Nawa({ apiKey: process.env.NAWA_API_KEY }) + +const { data, error } = await nawa.webhooks.create({ + url: 'https://example.com/webhooks/nawa', + events: ['classification.completed', 'credits.low'] +}) + +// Store the signing secret securely - it is shown only once +console.log(data.signing_secret) +``` + +```python Python +from nawa import Nawa + +nawa = Nawa(api_key="your_api_key") + +result = nawa.webhooks.create( + url="https://example.com/webhooks/nawa", + events=["classification.completed", "credits.low"] +) + +# Store the signing secret securely - it is shown only once +print(result.data.signing_secret) +``` + + + +### Success response (201) + +```json +{ + "success": true, + "result": { + "id": "550e8400-e29b-41d4-a716-446655440000", + "object": "webhook_endpoint", + "url": "https://example.com/webhooks/nawa", + "events": ["classification.completed", "credits.low"], + "signing_secret": "nawa_wh_a1b2c3d4e5f6...", + "active": true, + "created_at": "2026-01-15T12:00:00Z" + }, + "errors": [], + "request_id": "req_wh_abc123" +} +``` + + + The `signing_secret` is shown only at creation time. Store it securely. See [webhook signature verification](/webhooks#signature-verification) for usage. + + +--- + +## List webhook endpoints + +`GET /v1/webhooks` + +Retrieve all webhook endpoints for your account. + +### Example request + + + +```bash cURL +curl https://api.trynawa.com/v1/webhooks \ + -H "Authorization: Bearer nawa_live_sk_xxx" +``` + +```typescript TypeScript +const { data, error } = await nawa.webhooks.list() +``` + +```python Python +result = nawa.webhooks.list() +``` + + + +### Success response (200) + +```json +{ + "success": true, + "result": { + "object": "list", + "data": [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "object": "webhook_endpoint", + "url": "https://example.com/webhooks/nawa", + "events": ["classification.completed", "credits.low"], + "active": true, + "failure_count": 0, + "last_delivery_at": "2026-01-15T14:30:00Z", + "disabled_at": null, + "created_at": "2026-01-15T12:00:00Z" + } + ] + }, + "errors": [], + "request_id": "req_wh_list_abc123" +} +``` + +--- + +## Deactivate a webhook endpoint + +`DELETE /v1/webhooks?id={endpoint_id}` + +Deactivate a webhook endpoint. Deactivated endpoints stop receiving events. + +### Query parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `id` | string (UUID) | Yes | The webhook endpoint ID to deactivate. | + +### Example request + + + +```bash cURL +curl -X DELETE "https://api.trynawa.com/v1/webhooks?id=550e8400-e29b-41d4-a716-446655440000" \ + -H "Authorization: Bearer nawa_live_sk_xxx" +``` + +```typescript TypeScript +const { data, error } = await nawa.webhooks.delete('550e8400-e29b-41d4-a716-446655440000') +``` + +```python Python +result = nawa.webhooks.delete("550e8400-e29b-41d4-a716-446655440000") +``` + + + +### Success response (200) + +```json +{ + "success": true, + "result": { + "id": "550e8400-e29b-41d4-a716-446655440000", + "object": "webhook_endpoint", + "deleted": true + }, + "errors": [], + "request_id": "req_wh_del_abc123" +} +``` + +### Error responses + +| Status | Type | When | +|--------|------|------| +| 400 | `invalid_request_error` | Missing `url` or `events`, invalid URL scheme, too many endpoints | +| 401 | `authentication_error` | Invalid or missing API key | +| 404 | `not_found_error` | Webhook endpoint not found (DELETE only) | +| 429 | `rate_limit_error` | Rate limit exceeded | diff --git a/docs.json b/docs.json index 8f8f901..7b775f6 100644 --- a/docs.json +++ b/docs.json @@ -59,6 +59,7 @@ "api-reference/comments-reply", "api-reference/report", "api-reference/feedback", + "api-reference/webhooks-manage", "api-reference/comments-list", "api-reference/analytics", "api-reference/health", diff --git a/openapi.yaml b/openapi.yaml index 17706f2..f00d812 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -492,13 +492,26 @@ 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 + type: object + required: [name] + properties: + name: + type: string + description: Category name + description: + type: string + description: Optional description to guide the model + description: List of categories to classify against + multi_label: + type: boolean + default: false + description: "If true, return all matching categories above the threshold. If false, return only the best match." + confidence_threshold: + type: number + minimum: 0 + maximum: 1 + default: 0.5 + description: Minimum confidence score to include a category platform: type: string enum: [youtube, instagram, twitter, facebook] @@ -506,12 +519,17 @@ paths: 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" + 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" responses: "200": @@ -541,19 +559,18 @@ 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: allam + model: sdaia/allam-1-13b-instruct + fallback_used: false + cost_usd: 0.003 + credits_used: 3 errors: [] request_id: req_rub123abc456 "400": @@ -567,37 +584,46 @@ paths: "500": $ref: "#/components/responses/InternalError" - /comments/{id}/reply: + /comments/reply: post: - operationId: generateReply - summary: Generate reply to comment + operationId: commentsReply + summary: Classify and generate reply 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 contextual reply in a single call. + Supports tone control and max length configuration. For Arabic comments, + replies match the detected dialect. For English comments, replies are + natural and platform-appropriate. 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 + description: Force a specific AI provider (for A/B testing) schema: type: string - examples: - - "cmt_abc123" + enum: [claude, gemini, allam] requestBody: - required: false + required: true content: application/json: schema: type: object + required: [text] properties: + text: + type: string + minLength: 1 + description: Comment text to classify and reply to + examples: + - "هذا المنتج سيء جداً ولا أنصح به" + - "Great video, when is the next one?" tone: type: string enum: [friendly, professional, casual, formal] - default: friendly + default: professional description: Reply tone max_length: type: integer @@ -605,27 +631,18 @@ paths: maximum: 2000 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)." example: + text: "متى الجزء الثاني؟" tone: "friendly" - context: "Tech review channel focused on smartphones" + max_length: 500 responses: "200": - description: Generated reply + description: Classification and reply result 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-RateLimit-Limit: @@ -644,12 +661,21 @@ paths: 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: friendly + provider: allam + model: sdaia/allam-1-13b-instruct + cost_usd: 0.008 + credits_used: 8 errors: [] request_id: req_rep789xyz012 "400": @@ -681,13 +707,14 @@ paths: schema: $ref: "#/components/schemas/FeedbackInput" example: - request_id: "req_abc123def456" - field: "dialect" - expected_value: "levantine" - comment: "This is Lebanese Arabic, not Gulf" + classification_id: "cls_nw_a1b2c3d4e5f6" + rating: "incorrect" + corrected_intent: ["question"] + corrected_sentiment: "neutral" + comment: "This is a question, not praise" responses: "200": - description: Feedback accepted + description: Feedback acknowledged headers: X-Request-Id: $ref: "#/components/headers/X-Request-Id" @@ -697,13 +724,15 @@ paths: $ref: "#/components/schemas/FeedbackSuccessResponse" examples: accepted: - summary: Feedback accepted + summary: Feedback acknowledged value: success: true result: - feedback_id: fb_xyz789 - status: accepted - message: "Thank you! Your feedback helps improve NAWA's accuracy." + id: fb_nw_a1b2c3d4e5f6 + object: feedback + classification_id: cls_nw_a1b2c3d4e5f6 + rating: incorrect + acknowledged: true errors: [] request_id: req_fb_abc123 "400": @@ -996,25 +1025,25 @@ paths: - name: from in: query required: false + description: "Start date (ISO 8601). Default: start of current month." schema: type: string format: date-time - description: Start date (ISO 8601). Default: start of current month. - name: to in: query required: false + description: "End date (ISO 8601). Default: now." schema: type: string format: date-time - description: End date (ISO 8601). Default: now. - name: group_by in: query required: false + description: Group results by time period or endpoint schema: type: string enum: [day, week, month, endpoint] default: day - description: Group results by time period or endpoint responses: "200": description: Usage statistics @@ -1097,8 +1126,6 @@ paths: enum: - classification.completed - classification.failed - - comment.new - - comment.replied - credits.low - credits.exhausted responses: @@ -1687,91 +1714,143 @@ components: RubricResult: type: object + required: [id, object, categories, multi_label, language, provider, model, 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: Classification ID (rcl_nw_xxx format) + 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 + required: [name, confidence] + properties: + name: + type: string + description: Category name + confidence: + type: number + minimum: 0 + maximum: 1 + description: Confidence score + description: Matched categories with confidence scores + multi_label: + type: boolean + description: Whether multi-label mode was used language: type: string enum: [ar, en, mixed] + provider: + type: string + description: AI provider used model: type: string - cached: + fallback_used: type: boolean + cost_usd: + type: number + credits_used: + type: integer 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 (rpl_nw_xxx format) + 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 of the input comment + properties: + intent: + type: array + items: + type: string + description: Detected intents + sentiment: + type: string + enum: [positive, negative, neutral, mixed] + priority: + type: string + enum: [high, medium, low] + requires_response: + type: boolean + reply: + type: object + description: Generated reply + properties: + text: + type: string + description: The generated reply text + direction: + type: string + enum: [ltr, rtl] + description: Text direction of the reply + 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: Model used for generation + cost_usd: + type: number + description: Cost in USD + credits_used: + type: integer + description: Credits deducted FeedbackInput: type: object - required: [request_id, field, expected_value] + required: [classification_id, rating] properties: - request_id: + classification_id: type: string - description: The request_id from the original classification response - field: + description: The classification ID (cls_nw_xxx) from a classify response + rating: type: string - enum: [intent, sentiment, dialect, toxicity, category] - description: The field to correct - expected_value: + enum: [correct, incorrect, partial] + description: Whether the classification was correct, incorrect, or partially correct + corrected_intent: + type: array + items: + type: string + description: The correct intent(s) if the classification was wrong + corrected_sentiment: type: string - description: The correct value the model should have returned + enum: [positive, negative, neutral, mixed] + description: The correct sentiment if the classification was wrong comment: type: string description: Optional explanation of why this correction is needed FeedbackResult: type: object + required: [id, object, classification_id, rating, acknowledged] properties: - feedback_id: + id: type: string - status: + description: Feedback ID (fb_nw_xxx format) + object: type: string - enum: [accepted] - message: + const: feedback + classification_id: type: string + rating: + type: string + enum: [correct, incorrect, partial] + acknowledged: + type: boolean + const: true CommentItem: type: object diff --git a/webhooks.mdx b/webhooks.mdx index 2ef4922..7eea37b 100644 --- a/webhooks.mdx +++ b/webhooks.mdx @@ -12,8 +12,6 @@ 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 |