Create locked NpvTunnel configs
The Creator API imports configuration URIs, subscription links, JSON and open app exports, then creates signed, encrypted .npvs files for NpvTunnel on Android and iOS. Send the file to your users; only its selected audience can open it in NpvTunnel.
Base URL: https://api.npvtunnel.com
Seal configs: POST /v1/seal with a JSON request body and bearer authentication. The response is JSON.
Create your file
- You encode the complete config text as unpadded base64url.
- You send it to
POST /v1/sealwith an audience and optional restrictions. - You decode the returned
filevalue, save it as.npvs, and share it.
The API does not store your configs or generated files. Save the returned files so you can share them later.
Prepare the config
Save your configuration in configs.txt. Put one share link on each line for a file containing several profiles. This example uses a placeholder server; replace it with your own working configuration:
vless://[email protected]:443?security=tls&type=ws&path=%2Fvpn#Alpha
Encode that whole block once. Do not encode each line separately.
CONFIG=$(base64 < configs.txt | tr '+/' '-_' | tr -d '=\n')
The outer config field must always use unpadded base64url, including when the source is already base64 subscription text. Do not send plaintext or padded base64 in that field.
| Input | Accepted forms |
|---|---|
| Share links | vless://, vmess:// (standard and QR), trojan://, ss:// (SIP002 and legacy), socks://, socks5://, wireguard://, hysteria2://, hy2://, and npvt-ssh://. |
| Subscription contents | Plaintext share links or a base64-encoded list of links. Creates a fixed bundle of profiles. |
| Subscription link | One HTTPS URL. Creates a subscription that the recipient app can fetch and refresh. |
| Configuration files | Xray JSON, WireGuard configuration text, or an open NpvTunnel export. JSON and URI segments may be combined. |
Malformed or unsupported entries reject the entire request. For advanced settings that have no supported URI representation, use Xray JSON or an open app export.
Authentication
You need a creator API key to create files. Include it in each request:
Authorization: Bearer <CREATOR_API_KEY>
Keep this key private. Share the generated files with recipients, not your API key.
Create and save a config
This example uses a POSIX shell, curl, base64 and Node.js. Set NPV_CREATOR_API_KEY to your creator key and RECIPIENT_PUBLIC_KEY to the recipient's public key. Each recipient finds it in NpvTunnel under More → My public key.
Prepare CONFIG as above, then save the JSON response:
curl --fail-with-body --output response.json --request POST \
https://api.npvtunnel.com/v1/seal \
-H "Authorization: Bearer $NPV_CREATOR_API_KEY" \
-H "Content-Type: application/json" \
--data "{
\"config\": \"$CONFIG\",
\"recipientPublicKeys\": [\"$RECIPIENT_PUBLIC_KEY\"],
\"label\": \"weekly-alpha\",
\"lock\": {
\"onlyMobileNetwork\": true,
\"displayMessage\": \"Updated every Monday\"
}
}"
After a successful response, decode each file value and save it with the .npvs extension. These filenames are overwritten if you run the command again:
node -e '
const fs = require("node:fs");
const response = JSON.parse(fs.readFileSync("response.json", "utf8"));
response.configs.forEach((item, i) =>
fs.writeFileSync(`config-${i + 1}.npvs`, Buffer.from(item.file, "base64url"))
);'
Share the resulting .npvs files with your recipients to import into NpvTunnel.
JSON request body
| Field | Required | Meaning |
|---|---|---|
config | Unless subscription is supplied | Unpadded base64url of the complete UTF-8 config input. |
audience | No | recipients by default, everyone, or passphrase. |
recipientPublicKeys | For recipients | 1–100 public keys copied from recipients’ More → My public key screen in NpvTunnel. Optional for everyone and passphrase. |
passphrase | For passphrase audience | 1–1024 UTF-8 bytes, used exactly as supplied. Share it separately. |
subscription | Instead of config | Object with an HTTPS url, optional name, and optional autoUpdateHours (default 12, 0 disables automatic refresh). |
label | No | A name you can reuse when replacing a config. Maximum 64 ASCII characters using letters, digits, -, _, or .. |
lock | No | Optional expiry, mobile-data restriction and messages for recipients. |
requests | No | Use an array of the fields above to create several files in one call. Do not combine it with top-level config fields. |
Reuse a label within your creator account to update an existing config when the recipient imports the replacement file. Omit it to create a separate config. Updates require importing the new file; older copies are not automatically revoked. Each batch item supplies its own audience and restrictions.
Lock fields
| Field | Meaning |
|---|---|
expiresAt | When recipients must stop using the config in NpvTunnel. Use a future RFC 3339 timestamp, for example 2027-01-01T00:00:00Z, or omit for no expiry. This does not cancel their account at the VPN provider. |
onlyMobileNetwork | Allow connections only on mobile data when true. Defaults to false. |
displayMessage | Message shown when the config is opened. Maximum 512 bytes. |
customServerMessage | Message shown in place of the VPN server message. Maximum 512 bytes. |
Audiences
| Audience | Who can open it | Recipient keys |
|---|---|---|
recipients | Only devices whose public keys are listed. | Required |
everyone | Anyone using NpvTunnel. | Optional |
passphrase | Anyone with the passphrase, plus any listed recipient devices. | Optional |
For passphrase, supply a nonempty passphrase field and share it separately from the file. Named devices can open it without that passphrase. The field is invalid with any other audience. An empty recipient list never implies public access.
Use everyone for configs you are willing to make public. The file’s restrictions still apply.
Files with recipient keys use version 6 and require a current NpvTunnel app. Recipient access combines the device’s private key with the app’s generation-2 protection; the private key alone cannot open the file. Files without recipient keys use version 5. Existing v5 files remain readable but need to be re-exported to gain this protection.
Read the response
{
"configs": [{
"file": "BASE64URL_NPVS_FILE",
"sizeBytes": 742,
"audience": "recipients",
"wireVersion": 6,
"kind": "configs"
}],
"configsCreatedToday": 18,
"dailyConfigLimit": 2000
}
Each item in configs contains a file, in the same order as your requests. Decode file from base64url and save it as .npvs. sizeBytes is the saved file’s size, audience confirms who can open it, and kind tells you whether it contains configs or a subscription.
configsCreatedToday shows how many files you have created today. dailyConfigLimit is your account’s daily allowance. A file containing several profiles counts as one file.
Share a protected subscription
Share a subscription URL to let recipients get updated servers in NpvTunnel. Paste the subscription’s contents instead to share a fixed set of configs. Send each subscription URL in its own request item.
{
"subscription": {
"name": "My servers",
"url": "https://provider.example/subscription/token",
"autoUpdateHours": 12
},
"audience": "passphrase",
"passphrase": "a long private sharing phrase",
"lock": {"onlyMobileNetwork": true}
}
Use subscription instead of config to set the display name and refresh interval. The name is optional and limited to 512 UTF-8 bytes. autoUpdateHours defaults to 12; 0 disables automatic refresh. Use a whole number of hours. If you only need the default interval, you can send the HTTPS URL through config using the same base64url encoding as other inputs.
Use an HTTPS subscription URL without a username, password or #fragment in the URL. Recipients need access to the subscription provider to fetch updates.
Create several files in one call
Use requests to create 1–100 files. This example creates one public bundle and one passphrase-protected subscription. Replace the encoded-input placeholder with your CONFIG value, save the JSON as request.json, and send it using the command below.
{
"requests": [
{
"config": "BASE64URL_OF_COMPLETE_CONFIG_TEXT",
"audience": "everyone",
"label": "public-servers"
},
{
"subscription": {
"name": "Private servers",
"url": "https://provider.example/subscription/token"
},
"audience": "passphrase",
"passphrase": "a long private sharing phrase",
"label": "private-subscription"
}
]
}
curl --fail-with-body --output response.json \
https://api.npvtunnel.com/v1/seal \
-H "Authorization: Bearer $NPV_CREATOR_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @request.json
Use a JSON serializer when building requests in a bot or panel, so quotes and other characters in messages or passphrases are escaped correctly. Treat request files as sensitive because they contain your source configuration or passphrase.
Limits
- 100 config requests per API call
- 100 recipients per config
- 500 recipients across one API call
- 100 profiles per generated config file
- 256 KiB of decoded config input per file
- 4 MiB generated file, before base64url encoding
- 2 MiB request body
If a config is too large or complex, split it into smaller files and submit them separately.
Each API key also has per-minute, daily, and concurrent-request limits. Daily quota counts files and resets at midnight UTC. Invalid batches return no files and do not consume daily file quota; authenticated attempts still count toward the request rate. Successful responses include X-RateLimit-Limit and X-Daily-Config-Limit.
Errors
{
"error": "invalid_config",
"detail": "request 1: config must be unpadded base64url containing a UTF-8 config payload"
}
| Status | Error | Meaning |
|---|---|---|
| 400 | bad_request | Invalid JSON, unknown fields, or conflicting request forms. |
| 400 | invalid_config | Invalid config encoding, input, audience, key, label, or restriction. |
| 400 | too_many_recipients | More than 500 recipients in one API call. |
| 401 | unauthorized | Missing, invalid, disabled, or replaced API key. |
| 413 | request_too_large | Request body exceeds 2 MiB. |
| 429 | rate_limited, daily_limit_reached, or too_many_active_requests | An API-key limit was reached. |
| 503 | busy | The service is busy. Wait and retry. |
Use the HTTP status and error code to handle failures; detail explains the problem and may identify a batch item by its one-based position. Correct invalid input before resending the entire batch.
For temporary rate or concurrency limits, honor Retry-After when present. A daily limit requires waiting for the next UTC day or an account limit increase. Keep the X-Request-ID response header when requesting support. Repeating a successful request generates another file and consumes quota again, even with the same label.