NpvTunnel Creator API

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

  1. You encode the complete config text as unpadded base64url.
  2. You send it to POST /v1/seal with an audience and optional restrictions.
  3. You decode the returned file value, 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.

InputAccepted forms
Share linksvless://, vmess:// (standard and QR), trojan://, ss:// (SIP002 and legacy), socks://, socks5://, wireguard://, hysteria2://, hy2://, and npvt-ssh://.
Subscription contentsPlaintext share links or a base64-encoded list of links. Creates a fixed bundle of profiles.
Subscription linkOne HTTPS URL. Creates a subscription that the recipient app can fetch and refresh.
Configuration filesXray 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

FieldRequiredMeaning
configUnless subscription is suppliedUnpadded base64url of the complete UTF-8 config input.
audienceNorecipients by default, everyone, or passphrase.
recipientPublicKeysFor recipients1–100 public keys copied from recipients’ More → My public key screen in NpvTunnel. Optional for everyone and passphrase.
passphraseFor passphrase audience1–1024 UTF-8 bytes, used exactly as supplied. Share it separately.
subscriptionInstead of configObject with an HTTPS url, optional name, and optional autoUpdateHours (default 12, 0 disables automatic refresh).
labelNoA name you can reuse when replacing a config. Maximum 64 ASCII characters using letters, digits, -, _, or ..
lockNoOptional expiry, mobile-data restriction and messages for recipients.
requestsNoUse 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

FieldMeaning
expiresAtWhen 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.
onlyMobileNetworkAllow connections only on mobile data when true. Defaults to false.
displayMessageMessage shown when the config is opened. Maximum 512 bytes.
customServerMessageMessage shown in place of the VPN server message. Maximum 512 bytes.

Audiences

AudienceWho can open itRecipient keys
recipientsOnly devices whose public keys are listed.Required
everyoneAnyone using NpvTunnel.Optional
passphraseAnyone 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

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"
}
StatusErrorMeaning
400bad_requestInvalid JSON, unknown fields, or conflicting request forms.
400invalid_configInvalid config encoding, input, audience, key, label, or restriction.
400too_many_recipientsMore than 500 recipients in one API call.
401unauthorizedMissing, invalid, disabled, or replaced API key.
413request_too_largeRequest body exceeds 2 MiB.
429rate_limited, daily_limit_reached, or too_many_active_requestsAn API-key limit was reached.
503busyThe 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.