Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 138 additions & 7 deletions api-reference/report.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Generate a structured audience intelligence report from comment data. The report
| `tier` | string | No | `basic` or `pro`. Default: `basic`. Pro includes additional sections (IX-X). |
| `title` | string | No | Video or content title for context. |
| `channel_context` | string | No | Brief channel description to improve recommendation quality. |
| `stream` | boolean | No | Set to `true` for Server-Sent Events (SSE) streaming. Default: `false`. |

### Example request

Expand Down Expand Up @@ -176,13 +177,37 @@ print(result.data.sections["recommendations"])

### Error responses

| Status | Type | When |
|--------|------|------|
| 400 | `invalid_request_error` | Missing/invalid `comments`, wrong `tier`, fewer than 10 comments |
| 401 | `authentication_error` | Invalid or missing API key |
| 402 | `insufficient_credits` | Not enough credits (need 500 for Basic, 1500 for Pro) |
| 429 | `rate_limit_error` | Rate limit exceeded (5 reports/minute) |
| 500 | `api_error` | Internal or provider error |
| Status | Type | `error_code` | When |
|--------|------|--------------|------|
| 400 | `invalid_request_error` | -- | Missing/invalid `comments`, wrong `tier`, fewer than 10 comments |
| 401 | `authentication_error` | -- | Invalid or missing API key |
| 402 | `insufficient_credits` | -- | Not enough credits (need 500 for Basic, 1500 for Pro) |
| 429 | `rate_limit_error` | -- | Rate limit exceeded (5 reports/minute) |
| 500 | `api_error` | -- | Internal error |
| 503 | `service_unavailable` | `PROVIDER_FAILURE` | AI provider temporarily unavailable. Retry after `retry_after` seconds. |
| 503 | `service_unavailable` | `ARABIZI_NO_FALLBACK` | Arabizi request failed because Claude was unavailable and fallback providers are disabled for Arabizi. Retry after `retry_after` seconds. |

#### 503 response body

When the AI provider is temporarily unavailable, the response includes an `error_code` field so you can distinguish transient provider failures from Arabizi-specific failures.

```json
{
"error": "Chat temporarily unavailable -- please retry in a few seconds.",
"retry_after": 60,
"error_code": "ARABIZI_NO_FALLBACK"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `error` | string | Human-readable error message |
| `retry_after` | integer | Seconds to wait before retrying |
| `error_code` | string | `ARABIZI_NO_FALLBACK` when the request contained Arabizi text and Claude failed, or `PROVIDER_FAILURE` for general provider failures |

<Note>
**Why does Arabizi disable fallback?** NAWA uses a script-mirror hint to produce Arabizi output. Only Claude honors this hint. If Claude is unavailable, falling back to another provider would silently return Arabic or English text instead of the requested Arabizi, which is incorrect. NAWA retries Claude once with a short backoff, then returns this error so you can retry on your side.
</Note>

### Report tier comparison

Expand All @@ -201,3 +226,109 @@ print(result.data.sections["recommendations"])
Best for: Agency client reports, content strategy planning, deep audience analysis.
</Accordion>
</AccordionGroup>

## Streaming

Set `stream: true` in your request body to receive the report as Server-Sent Events (SSE). Each event contains a `chunk` of the report text as it is generated, allowing you to display partial results to your users.

### SSE event types

| Event | Payload | Description |
|-------|---------|-------------|
| `chunk` | `{ "text": "..." }` | A piece of the report markdown |
| `done` | Completion metadata | Final event with provider, latency, and token usage |
| `error` | `{ "error": "...", "error_code": "...", "retry_after": 60 }` | An error occurred during generation |

### Streaming error events

When streaming fails, NAWA sends an SSE `error` event before closing the connection. The `error_code` field tells you whether to retry immediately or show a specific message to your users.

```
event: error
data: {"error":"Chat temporarily unavailable -- please retry in a few seconds.","error_code":"ARABIZI_NO_FALLBACK","retry_after":60}
```

| `error_code` | Meaning |
|--------------|---------|
| `ARABIZI_NO_FALLBACK` | The request contained Arabizi text and Claude was unavailable. Fallback providers are disabled for Arabizi because only Claude can produce correct Arabizi output. |
| `PROVIDER_FAILURE` | A general provider failure. You can safely retry after `retry_after` seconds. |

### Handling 503 errors

<CodeGroup>

```bash cURL
curl -s -X POST https://api.trynawa.com/v1/report \
-H "Authorization: Bearer nawa_live_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"comments": [
{"text": "يا زلمة هاد الفيديو نار!", "author": "User1", "likes": 10},
{"text": "وين الترجمة؟", "author": "User2", "likes": 5}
],
"tier": "basic"
}'

# On 503, the response includes error_code and retry_after:
# {"error":"AI service temporarily unavailable","retry_after":60,"error_code":"PROVIDER_FAILURE"}
# {"error":"Chat temporarily unavailable -- please retry in a few seconds.","retry_after":60,"error_code":"ARABIZI_NO_FALLBACK"}
```

```typescript TypeScript
import { Nawa } from '@nawalabs/sdk'

const nawa = new Nawa({ apiKey: process.env.NAWA_API_KEY })

const { data, error } = await nawa.report({
comments: [
{ text: 'يا زلمة هاد الفيديو نار!', author: 'User1', likes: 10 },
// ... min 10 comments
],
tier: 'basic',
})

if (error) {
if (error.error_code === 'ARABIZI_NO_FALLBACK') {
// Arabizi request failed because Claude was unavailable.
// Retry after the suggested delay.
console.log(`Arabizi request failed. Retry in ${error.retry_after}s`)
} else if (error.error_code === 'PROVIDER_FAILURE') {
console.log(`Provider unavailable. Retry in ${error.retry_after}s`)
}
}
```

```python Python
import requests
import time

response = requests.post(
"https://api.trynawa.com/v1/report",
headers={
"Authorization": "Bearer nawa_live_sk_xxx",
"Content-Type": "application/json",
},
json={
"comments": [
{"text": "يا زلمة هاد الفيديو نار!", "author": "User1", "likes": 10},
# ... min 10 comments
],
"tier": "basic",
},
)

if response.status_code == 503:
body = response.json()
error_code = body.get("error_code")
retry_after = body.get("retry_after", 60)

if error_code == "ARABIZI_NO_FALLBACK":
print(f"Arabizi request failed. Retry in {retry_after}s")
else:
print(f"Provider unavailable. Retry in {retry_after}s")

time.sleep(retry_after)
# Retry the request...
```

</CodeGroup>
56 changes: 56 additions & 0 deletions errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,48 @@ An unexpected error occurred on the NAWA side.
</Accordion>
</AccordionGroup>

### `service_unavailable` (503)

The AI provider is temporarily unavailable. The response includes a `retry_after` field with the number of seconds to wait before retrying, and an `error_code` field to distinguish between failure types.

<AccordionGroup>
<Accordion title="PROVIDER_FAILURE">
**Cause:** The primary AI provider failed and no fallback was available.

**Fix:** Wait for `retry_after` seconds, then retry.

```json
{
"error": "AI service temporarily unavailable",
"retry_after": 60,
"error_code": "PROVIDER_FAILURE"
}
```
</Accordion>

<Accordion title="ARABIZI_NO_FALLBACK">
**Cause:** The request contained Arabizi text (Arabic written in Latin script) and Claude was unavailable. NAWA disables fallback providers for Arabizi because only Claude honors the script-mirror hint that produces correct Arabizi output. Falling back to another provider would silently return Arabic or English text instead.

NAWA retries Claude once with a short backoff before returning this error.

**Fix:** Wait for `retry_after` seconds and retry the request. This is a transient failure.

```json
{
"error": "Chat temporarily unavailable -- please retry in a few seconds.",
"retry_after": 60,
"error_code": "ARABIZI_NO_FALLBACK"
}
```

In streaming mode, this error is delivered as an SSE event:
```
event: error
data: {"error":"Chat temporarily unavailable -- please retry in a few seconds.","error_code":"ARABIZI_NO_FALLBACK","retry_after":60}
```
</Accordion>
</AccordionGroup>

---

## Handling errors in code
Expand Down Expand Up @@ -297,6 +339,14 @@ if (error) {
case 'insufficient_credits':
console.error('Buy credits:', error.suggested_action)
break
case 'service_unavailable':
// Check error_code for Arabizi-specific failures
if (error.error_code === 'ARABIZI_NO_FALLBACK') {
console.log(`Arabizi unavailable, retry in ${error.retry_after}s`)
} else {
console.log(`Provider down, retry in ${error.retry_after}s`)
}
break
default:
console.error('API error:', error.message)
}
Expand All @@ -318,6 +368,12 @@ if result.error:
print(f"Rate limited, retry after: {result.error.retry_after}")
elif result.error.type == "insufficient_credits":
print(f"Buy credits: {result.error.suggested_action}")
elif result.error.type == "service_unavailable":
# Check error_code for Arabizi-specific failures
if result.error.error_code == "ARABIZI_NO_FALLBACK":
print(f"Arabizi unavailable, retry in {result.error.retry_after}s")
else:
print(f"Provider down, retry in {result.error.retry_after}s")
else:
print(f"API error: {result.error.message}")
```
Expand Down