Pre-flight checklist
Review this first to avoid going back later.
- An email address capable of receiving mail, used for registration.
- Any of the following runtimes on your machine: Python 3, JDK 15 and above (examples use text blocks), Go, or PHP with the curl extension.
- Confirm you are 18+; the service is for adults only.
- Know you want plain text chat: there is only one model, no embeddings, images, voice, or fine-tuning.
- Store your API key in environment variables, not in source code or commits.
Few interface conventions: endpoint https://api.wushenchaapi.com/v1, compatible with OpenAI chat completions. Your existing request bodies should work as-is.
Estimated time: registration takes one minute, environment setup depends on your machine, and the first request itself takes no more than thirty seconds. If you haven't received a result after this time, it is likely a network or key issue; go straight to the troubleshooting list at the end. Follow this order: models, then chat; synchronous first, then streaming; confirm each step before proceeding.
Register and get your key
Open /get-api-key/ and register with your email and password. The key is displayed immediately after registration; copy it down. A few details:
- Each account has only one API key.
- You can regenerate it, but the old key expires immediately. Deploy the new value before switching in production.
- New accounts receive $0.50 in trial credit, valid for 7 days, with no payment information required.
- When free trial credit expires or is used up, requests return 402 with error code
no_credit. Top up prepaid credit to continue. It is not a subscription; balance never expires.
Set the API key in environment variables: macOS/Linux run export API_KEY=your_key, Windows PowerShell uses $env:API_KEY="your_key". All examples read from API_KEY.
Step 1: Verify connectivity with /v1/models
Don't send chat requests yet. Make a zero-cost GET request to confirm address, network, and API key are correct:
curl https://api.wushenchaapi.com/v1/models \
-H "Authorization: Bearer $API_KEY"A 200 response with the model list (only one uncensored) means it works. 401 means invalid or missing API key; timeout means check network/proxy. Separating connectivity from request body issues saves time during integration.
Python: requests
No SDK needed; one requests.post is enough. Note three things: set timeout; check r.ok before accessing choices; on failure, the error body is {"error":{"code":...,"message":...}}.
import os
import requests
url = "https://api.wushenchaapi.com/v1/chat/completions"
headers = {
"Authorization": "Bearer " + os.environ["API_KEY"],
"Content-Type": "application/json",
}
payload = {
"model": "uncensored",
"messages": [{"role": "user", "content": "用两句话描述一场雨夜里的追逐戏。"}],
"max_tokens": 300,
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
print(r.status_code)
data = r.json()
if r.ok:
print(data["choices"][0]["message"]["content"])
print(data["usage"])
else:
print(data["error"]["code"], data["error"]["message"])On success, usage contains prompt_tokens and completion_tokens. Print them during development to track costs.
Request body breakdown
All four languages send the same JSON. Understand it first; writing in any language is just translation.
| Field | Required | Description |
|---|---|---|
| model | Yes | Fixed to uncensored. |
| messages | Yes | Array of objects with role and content. Roles are system, user, assistant. |
| max_tokens | No | Default 2048, max 16,000 per request. Increase it for long texts. |
| temperature / top_p / stop | No | Standard sampling parameters, passed through as-is. |
| stream | No | Set to true for SSE streaming. |
You will most often read three fields in the response: choices[0].message.content is the body, choices[0].finish_reason tells you if it ended normally or was truncated by length, and usage is the token usage for this request. Reading all three fields ensures a complete integration.
Also note two hard limits: the request body must not exceed 8 MB, and each API key is limited to 300 requests per minute. For batch tasks, do not send all parallel requests at once; set a simple concurrency cap first.
Java: java.net.http
JDK 11+ includes HttpClient out of the box, so no dependencies are needed. The example below uses JDK 15 text blocks for JSON; for lower versions, you can use string concatenation or a JSON library of your choice to serialize.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Demo {
public static void main(String[] args) throws Exception {
String key = System.getenv("API_KEY");
String body = """
{"model":"uncensored",
"messages":[{"role":"user","content":"写一段两百字以内的悬疑小说开头。"}],
"max_tokens":400}
""";
HttpRequest req = HttpRequest.newBuilder(
URI.create("https://api.wushenchaapi.com/v1/chat/completions"))
.timeout(Duration.ofSeconds(120))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> resp = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}Tip: When the body contains Chinese, BodyPublishers.ofString defaults to UTF-8 encoding, so no extra handling is needed. In production, make HttpClient a singleton to reuse it, instead of creating a new instance for every request.
Go: net/http
Go's standard library is equally sufficient. The key point is not to use the default http.DefaultClient without setting a timeout; otherwise, goroutines will hang indefinitely if the server stalls.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
body := []byte(`{"model":"uncensored","messages":[{"role":"user","content":"给一个反派角色写三句独白。"}],"max_tokens":300}`)
req, err := http.NewRequest("POST", "https://api.wushenchaapi.com/v1/chat/completions", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 120 * time.Second}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(out))
}Read the response body completely using io.ReadAll before parsing. For structured processing, define a corresponding struct and deserialize it using encoding/json. The fields are choices, message, content, and usage.
PHP: curl
Use the curl extension in PHP. Remember to add JSON_UNESCAPED_UNICODE to json_encode; otherwise, Chinese characters will be escaped as \uXXXX. It works, but it is hard to read during debugging.
<?php
$payload = [
"model" => "uncensored",
"messages" => [["role" => "user", "content" => "写一首八行的现代诗,主题是末班地铁。"]],
"max_tokens" => 300,
];
$ch = curl_init("https://api.wushenchaapi.com/v1/chat/completions");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$res = curl_exec($ch);
if ($res === false) {
die("curl 错误: " . curl_error($ch) . "\n");
}
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $status, "\n";
$data = json_decode($res, true);
echo $data["choices"][0]["message"]["content"] ?? $res, "\n";Distinguish between two types of failures: curl_exec returning false indicates a network-layer issue; returning content with a non-200 status code indicates an API-layer issue, so read error.code.
Common pitfalls on the first call
- 401: The header was written as
Authorization: <key>, missing theBearerprefix; or the environment variable did not take effect in the new terminal. - 404: The path is missing
/v1, orchat/completionsis misspelled. - 400: Invalid JSON, or the prompt plus max_tokens exceeds the 100,000 token limit.
- 402: Balance depleted or free trial credit expired.
- Output truncated:
max_tokensdefaults to 2048, with a maximum of 16,000 per request. Increase it explicitly for long texts. - Want streaming: Add
"stream": trueto the request body. The response will be SSE, and a data block with usage will be appended at the end.
Once it works, look at Prompt writing to improve output quality. For errors, consult the Error Code Troubleshooting Guide. Full parameter documentation is in the documentation, and pricing is on the pricing page.
Once it works, add three things before going live
The examples in the tutorial are the minimum viable versions. To put them into production, you must add at least these three items.
- Timeout. The example sets a uniform 120-second timeout because long text generation is indeed slow. If your business requires synchronous waiting, shorten the timeout according to your page's tolerance and use streaming output so users can see characters first.
- Error routing. 401, 402, and 403 are errors that retrying won't fix; alert or notify the user. Only 429 and 503 are worth backing off and retrying. Specific implementations are in the troubleshooting guide and are not detailed here.
- Usage logging. Log the usage field from every response. Log only the numbers; do not log user content. This is essential for end-of-month reconciliation and investigating abnormal consumption.
A note on key management: there is only one key. If leaked, it can only be regenerated, and regenerating it will cause all active services to go offline. So do not scatter it across multiple scripts and machine configurations; centralize it in one key configuration location so you don't miss any services when switching.
Finally, content boundaries: legal adult content, fictional novels, and controversial topics are not rejected. However, sexual content involving minors is always blocked with a 403 response, including in fiction and roleplay. If your application targets adult users, we recommend adding age verification in your own registration flow.
Here is a final self-check sequence you can even paste next to your monitor: Is the key in the environment variables? Do models respond? Is the request body valid JSON? Is the model uncensored? Is max_tokens sufficient? Is finish_reason in the response 'stop' or 'length'? Is usage logged? If all seven items are green, your integration is complete.