diff --git a/api-reference/classify.mdx b/api-reference/classify.mdx index a22db4b..9e7be9e 100644 --- a/api-reference/classify.mdx +++ b/api-reference/classify.mdx @@ -143,8 +143,8 @@ result = nawa.classify(text="متى الجزء الثاني؟") |--------|------|------| | 400 | `invalid_request_error` | Missing or empty `text`, invalid `provider` query param, malformed JSON body | | 401 | `authentication_error` | Missing, malformed, revoked, or expired API key | -| 402 | `insufficient_credits` | Live key has insufficient credit balance for this request. Response body includes `balance_credits` and `required_credits` on the error object. | -| 429 | `rate_limit_error` | Per-minute rate limit exceeded, or sandbox lifetime quota (100 requests) exhausted | +| 402 | `insufficient_credits` | Live key has insufficient credit balance. Checked before any AI work runs (but after the cache check, so cache hits remain free). Response body includes `balance_credits` and `required_credits`. | +| 429 | `rate_limit_error` | Per-minute rate limit exceeded, or free key lifetime quota (100 requests) exhausted | | 500 | `api_error` | Unexpected internal failure. The response `message` is a generic constant; quote `request_id` to support for the underlying cause. | See [Errors](/errors) for the full envelope shape and all error codes. diff --git a/api-reference/comments-reply.mdx b/api-reference/comments-reply.mdx index d6321d7..195550d 100644 --- a/api-reference/comments-reply.mdx +++ b/api-reference/comments-reply.mdx @@ -89,3 +89,27 @@ result = nawa.comments.reply( | `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 | + +### Response headers + +| Header | Description | +|--------|-------------| +| `X-Request-Id` | Unique request identifier. Quote this to support when reporting an issue. | +| `X-NAWA-Cache` | `HIT` if served from the semantic cache (free, no credit charge), `MISS` otherwise. | +| `X-NAWA-Provider` | Which provider produced the response: `claude`, `allam`, `gemini`. | +| `X-NAWA-Balance` | Current credit balance in USD. | +| `X-RateLimit-Limit` | Your tier's per-minute request ceiling. | +| `X-RateLimit-Remaining` | Requests remaining in the current one-minute window. | +| `X-RateLimit-Reset` | RFC 3339 timestamp when the window resets. | + +### Error responses + +| Status | Type | When | +|--------|------|------| +| 400 | `invalid_request_error` | Missing comment ID, invalid `tone` or `language`, malformed JSON body | +| 401 | `authentication_error` | Missing, malformed, revoked, or expired API key | +| 402 | `insufficient_credits` | Live key has insufficient credit balance. Checked before any AI work runs. Response body includes `balance_credits` and `required_credits`. | +| 429 | `rate_limit_error` | Per-minute rate limit exceeded, or free key lifetime quota (100 requests) exhausted | +| 500 | `api_error` | Unexpected internal failure. The response `message` is a generic constant; quote `request_id` to support for the underlying cause. | + +See [Errors](/errors) for the full envelope shape and all error codes. diff --git a/api-reference/detect.mdx b/api-reference/detect.mdx index 2169b2c..f11da3b 100644 --- a/api-reference/detect.mdx +++ b/api-reference/detect.mdx @@ -149,6 +149,8 @@ curl -X POST https://api.trynawa.com/v1/detect \ |--------|------|------| | 400 | `invalid_request_error` | Missing `text`. Text too long. | | 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 error | +| 402 | `insufficient_credits` | Live key has insufficient credit balance. Checked before any AI work runs (but after the cache check, so cache hits remain free). Response body includes `balance_credits` and `required_credits`. | +| 429 | `rate_limit_error` | Per-minute rate limit exceeded, or free key lifetime quota (100 requests) exhausted | +| 500 | `api_error` | Unexpected internal failure. The response `message` is a generic constant; quote `request_id` to support for the underlying cause. | + +See [Errors](/errors) for the full envelope shape and all error codes. diff --git a/api-reference/moderate.mdx b/api-reference/moderate.mdx index 947d30e..7171084 100644 --- a/api-reference/moderate.mdx +++ b/api-reference/moderate.mdx @@ -267,6 +267,8 @@ curl -X POST https://api.trynawa.com/v1/moderate \ |--------|------|------| | 400 | `invalid_request_error` | Missing `text`. Invalid strictness or platform. Text too long. | | 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 | +| 402 | `insufficient_credits` | Live key has insufficient credit balance. Checked before any AI work runs. Response body includes `balance_credits` and `required_credits`. | +| 429 | `rate_limit_error` | Per-minute rate limit exceeded, or free key lifetime quota (100 requests) exhausted | +| 500 | `api_error` | Unexpected internal failure. The response `message` is a generic constant; quote `request_id` to support for the underlying cause. | + +See [Errors](/errors) for the full envelope shape and all error codes. diff --git a/api-reference/report.mdx b/api-reference/report.mdx index 74662dc..902c27f 100644 --- a/api-reference/report.mdx +++ b/api-reference/report.mdx @@ -8,9 +8,13 @@ api: "POST https://api.trynawa.com/v1/report" Generate a structured audience intelligence report from comment data. The report analyzes audience clusters, dominant narratives, sentiment patterns, spam/bot detection, and produces data-backed strategic recommendations. - Cost: **$0.50** per Basic report (500 credits). **$1.50** per Pro report (1500 credits). Pro includes content brief, repeat commenter analysis, and engagement playbook. + Cost: **$0.50** per Basic report (500 credits). **$1.50** per Pro report (1,500 credits). Pro includes content brief, repeat commenter analysis, and engagement playbook. + + Reports require a **live API key** (`nawa_live_sk_`). Free keys are blocked because the minimum cost is 500 credits. Your balance is checked before any AI work runs, so you are never charged for a request that fails due to insufficient credits. + + ## Request ### Headers @@ -194,9 +198,11 @@ Every percentage in report tables carries one of three tags: |--------|------|------| | 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 | +| 402 | `insufficient_credits` | Live key has insufficient credit balance (need 500 for Basic, 1,500 for Pro). Checked before any AI work runs. Response body includes `balance_credits` and `required_credits`. | +| 429 | `rate_limit_error` | Rate limit exceeded (5 reports/minute, 20/day) | +| 500 | `api_error` | Unexpected internal failure. The response `message` is a generic constant; quote `request_id` to support for the underlying cause. | + +See [Errors](/errors) for the full envelope shape and all error codes. ### Report tier comparison diff --git a/billing.mdx b/billing.mdx index e28bb54..f094c6c 100644 --- a/billing.mdx +++ b/billing.mdx @@ -78,15 +78,19 @@ Every API response includes balance information in headers: ## What happens when credits run out -When your credit balance reaches 0: +NAWA runs a pre-call balance gate on every paid endpoint. Your balance is checked **before** any AI model work runs, so you are never charged for a request that fails due to insufficient credits. -- All paid endpoints return `402 insufficient_credits` -- Free endpoints continue to work normally -- Free keys (`nawa_test_sk_`) are unaffected -- they use their own 100-request pool +When your credit balance is too low for a request: + +- The endpoint returns `402 insufficient_credits` with `balance_credits` and `required_credits` in the error body +- No AI work executes and no credits are deducted +- Free endpoints (health, usage, analytics, comments, feedback) continue to work normally +- Free keys (`nawa_test_sk_`) are unaffected -- they use their own 100-request pool and do not consume credit balance +- Semantic cache hits remain free even on paid endpoints. A cache hit never triggers the balance gate. - Purchase any credit pack to restore access immediately - There is no grace period. When credits hit zero, paid endpoints stop immediately. Enable monitoring on your `X-NAWA-Balance` header to avoid service interruption. + There is no grace period. When credits are insufficient, paid endpoints stop immediately. Enable monitoring on your `X-NAWA-Balance` header to avoid service interruption. ## Cost estimation diff --git a/errors.mdx b/errors.mdx index fdcf365..9a98583 100644 --- a/errors.mdx +++ b/errors.mdx @@ -181,16 +181,28 @@ Your account balance is insufficient for the requested operation. - **Cause:** Your credit balance has reached $0. There is no grace period -- paid endpoints stop immediately. + **Cause:** Your credit balance is too low for the requested operation. The balance is checked before any work runs, so you are never charged for a request that fails with this error. - **Fix:** Purchase a credit pack from the dashboard to restore access instantly. + **Fix:** Purchase a credit pack from the dashboard to restore access instantly. Free keys are unaffected; they use a separate 100-request pool. + + The error object includes two extra fields so your client can render a precise upgrade prompt without a second round trip: + + | Field | Type | Description | + |-------|------|-------------| + | `balance_credits` | number | Your current credit balance | + | `required_credits` | number | Credits this request would have consumed | ```json { "type": "insufficient_credits", "code": "balance_exhausted", - "message": "Credit balance exhausted. Purchase credits to continue.", - "suggested_action": "Buy a credit pack at trynawa.com/developers/keys." + "message": "Insufficient credits: balance 3, required 6", + "display_message": "Your credit balance is insufficient for this request.", + "balance_credits": 3, + "required_credits": 6, + "param": null, + "doc_url": "https://developers.trynawa.com/errors#insufficient_credits", + "suggested_action": "Purchase credits at trynawa.com/developers/keys." } ``` @@ -242,14 +254,15 @@ An unexpected error occurred on the NAWA side. **Cause:** An unexpected internal failure. - **Fix:** Retry the request. If the issue persists, contact support with the `request_id`. + **Fix:** Retry the request. If the issue persists, contact support with the `request_id`. The `message` field is always a generic constant for security. Raw error details are never exposed to API clients. NAWA logs the underlying cause server-side and correlates it with your `request_id`, so support can investigate without you needing to share sensitive data. ```json { "type": "api_error", "code": "internal_error", - "message": "An unexpected error occurred. Please retry.", - "suggested_action": "Retry the request. Contact support if the issue persists." + "message": "An unexpected error occurred. If this persists, contact support with the request_id.", + "display_message": "Something went wrong processing your request.", + "suggested_action": "Contact support if this persists." } ``` @@ -295,6 +308,7 @@ if (error) { console.log('Rate limited, retrying after:', error.retryAfter) break case 'insufficient_credits': + console.error(`Balance: ${error.balance_credits}, need: ${error.required_credits}`) console.error('Buy credits:', error.suggested_action) break default: @@ -317,6 +331,7 @@ if result.error: elif result.error.type == "rate_limit_error": print(f"Rate limited, retry after: {result.error.retry_after}") elif result.error.type == "insufficient_credits": + print(f"Balance: {result.error.balance_credits}, need: {result.error.required_credits}") print(f"Buy credits: {result.error.suggested_action}") else: print(f"API error: {result.error.message}") diff --git a/openapi.yaml b/openapi.yaml index 2c6201d..5a4cf6d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2258,6 +2258,7 @@ components: enum: - authentication_error - permission_error + - insufficient_credits - rate_limit_error - invalid_request_error - not_found_error @@ -2325,13 +2326,15 @@ components: example: type: error error: - type: permission_error - code: insufficient_credits - message: Credit balance is $0.00 - display_message: Your account has insufficient credits. + type: insufficient_credits + code: balance_exhausted + message: "Insufficient credits: balance 3, required 6" + display_message: Your credit balance is insufficient for this request. param: null - doc_url: https://developers.trynawa.com/billing + doc_url: https://developers.trynawa.com/errors#insufficient_credits suggested_action: Purchase credits at trynawa.com/developers/keys. + balance_credits: 3 + required_credits: 6 request_id: req_nw_a1b2c3d4e5f67890 RateLimited: @@ -2376,3 +2379,14 @@ components: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + example: + type: error + error: + type: api_error + code: internal_error + message: "An unexpected error occurred. If this persists, contact support with the request_id." + display_message: Something went wrong processing your request. + param: null + doc_url: https://developers.trynawa.com/api/errors + suggested_action: Contact support if this persists. + request_id: req_nw_a1b2c3d4e5f67890