Eric
February 1, 2026, 2:35pm
1
Hello Team,
Currently trying to set sdk using NodeJS version 20.20.0. Identity has been created and well enrolled but when I run below code, result is "auth ziti.init() failed with return code -13".
import ziti from '@openziti/ziti-sdk-nodejs';
const identityFile = './identity.json';
// Initialisez l'identité
await ziti.init(identityFile).catch(err => {
console.error('Échec auth', err);
process.exit(1);
});
I guess the issue is in the identity.son file but I don't see what's wrong:
{
"ziti": {
"version": 3,
"identity": {
"cert": "-----BEGIN CERTIFICATE----- xxx -----END CERTIFICATE-----",
"key": "-----BEGIN RSA PRIVATE KEY----- xxx -----END RSA PRIVATE KEY-----",
"controller": "https://my.controller.fr:8440/edge/client/v1"
}
}
I tried to add the CA (NetFoundry) to this identity file without success.
Any idea where is the issue?
Thanks.
ekoby
February 2, 2026, 2:15pm
2
Eric,
Please, provide more information:
platform (OS/arch)
ziti (controller/router) version
SDK version
Sometimes when working with nodejs SDK it is informative to set ZITI_LOG env var. set it to a number between 1 and 6, with higher number being more verbose.
also, if you're trying SDK v0.23.1 -- there was an issue with publishing on some platforms and it was fixed in v0.24.0 out yesterday.
Eric
February 3, 2026, 6:39am
3
Hi Ekoby,
Platform: Debian 13.3 ARM
Ziti controller / router version : 1.6.6
SDK version 0.23.1
I'll try with 0.24 and tell you.
Thanks,
Eric
February 3, 2026, 1:06pm
4
Eugene,
Following previous message, I have upgraded ziti-sdk-nodejs to 0.24.1 and got same result.
Best regards,
ekoby
February 3, 2026, 2:19pm
5
please enable logging
wait.. where did you get this identity file from? It is not in the correct format
Eric
February 3, 2026, 2:40pm
6
Well, I cut some data in "cert" and "key" keys to make my post shorter and I changed the controller name. Do you need the real comprehensive file?
ekoby
February 3, 2026, 2:51pm
7
no, the JSON fields are wrong for an identity file. Did you manually create it?
the correct format is like this:
{
"ztAPI":"https://<your-controller>:443",
"ztAPIs":[
"https://<your controller>:443"
],
"id":{
"cert":"-----BEGIN CERTIFICATE-----<snip>-----END CERTIFICATE-----\n",
"key":"-----BEGIN PRIVATE KEY-----<snip>-----END PRIVATE KEY-----\n",
"ca":"-----BEGIN CERTIFICATE----<snip>-----END CERTIFICATE-----\n"
}
}
Eric
February 3, 2026, 2:51pm
8
eric@debian:~/sdk$ ZITI_LOG=5 node test.js
(30480)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=5/VERBOSE
(30480)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5 (HEAD) starting at (2026-02-03T14:50:38.700)
Échec auth ziti.init() failed with return code -13
eric@debian:~/sdk$
Eric
February 3, 2026, 3:10pm
10
Oups
rl.question('JWT file name: ', async (filename) => {
try {
const token = await fs.promises.readFile(filename, 'utf8');
const payload = decodeJWT(token.trim());
const mon_csr = generateCSR();
console.log(`jti : ${payload.jti}`);
console.log(my_csr);
// Envoi de la requête POST
const response = await fetch(`https://my.server.eu:8440/edge/client/v1/enroll?method=ott&token=${payload.jti}`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-pem-file' },
body: mon_csr
});
// Extraction du certificat client et du certificat de l'aurtorité intermédiaire
const data = await response.json(); // Attendre la réponse JSON
let cert = data.data.cert; // Extraire le certificat
fs.writeFileSync('client.cert', cert);
console.log('Certificat sauvegardé dans client.cert');
// Lire les fichiers
cert = fs.readFileSync('client.cert', 'utf8');
const key = fs.readFileSync('private-key.pem', 'utf8');
// Structure de l'identité Ziti
const identity = {
ziti: {
version: 3,
identity: {
cert: cert.trim(),
key: key.trim(),
controller: "https://my.server.eu:8440/edge/client/v1"
}
}
};
// Création du fichier identity.json
fs.writeFileSync('identity.json', JSON.stringify(identity, null, 2));
console.log('Fichier identity.json créé avec succès');
} catch (err) {
console.error('Erreur :', err.message);
} finally {
rl.close();
}
});
When I use this code, the identity is shown as enroled in the Ziti Web console.
ekoby
February 3, 2026, 3:35pm
11
looks like you implement your own enrollment flow.
you should probably use ziti.ziti_enroll() provided by the SDK. see the sample here ziti-sdk-nodejs/tests/enroll-test.js at main · openziti/ziti-sdk-nodejs · GitHub
Eric
February 3, 2026, 3:54pm
12
Sure, I'll try to use this code. I don't remember where I found this identity file description.
Thanks a lot.
Eric
February 8, 2026, 1:09pm
13
Hi Eugene,
I'm stuck despite many things done. I use this code for enrollment:
const ziti = require('../ziti-sdk-nodejs/build/Release/ziti_sdk_nodejs.node');
const result = ziti.ziti_sdk_version();
console.assert(result, "Échec : version vide");
console.log("Version Ziti :", result);
const ziti_Enroll = async (jwt_path) => {
console.log("JS ziti_Enroll() entered ")
return new Promise((resolve, reject) => {
let rc = ziti.ziti_enroll(
jwt_path,
(data) => {
return resolve(data);
}
);
});
};
(async () => {
let jwt_path = process.argv[2];
let data = await ziti_Enroll(jwt_path).catch((data) => {
console.log('JS ziti_enroll failed with error code (%o/%s)', data.status, data.err);
});
if (data && data.identity) {
console.log("data.identity is:\n\n%s\n", data.identity);
}
process.exit(0);
})();
Here is the result:
eric@testSdk:~/testSdk$ ZITI_LOG=5 node testZiti.js testSdk.jwt
(97924)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=5/VERBOSE
(97924)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting at (2026-02-08T13:04:18.907)
(97924)[ 0.000] INFO ziti-njs:ziti-add-on.c:47 Init() Ziti NodeJS SDK version 0.24.2@g325edb5(HEAD) starting at (2026-02-08T13:04:18.907)
(97924)[ 0.000] DEBUG ziti-njs:ziti-add-on.c:51 Init()
Version: 0.24.2
Build Date: 2026-02-08T07:08:24Z
Git Branch: main
Git SHA: 6168bcc
OS: Linux
Arch: x86_64
Version Ziti : 1.10.9
JS ziti_Enroll() entered
(97924)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=5/VERBOSE
(97924)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting at (2026-02-08T13:04:18.913)
(97924)[ 0.000] DEBUG ziti-njs:ziti_enroll.c:160 _ziti_enroll() entered
(97924)[ 0.000] DEBUG ziti-njs:ziti_enroll.c:186 _ziti_enroll() args[1] IS a napi_function
(97924)[ 0.000] INFO ziti-sdk:ziti_enroll.c:112 ziti_enroll() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting enrollment at (2026-02-08T13:04:18.913)
(97924)[ 0.000] VERBOSE ziti-sdk:utils.c:642 load_file() path[testSdk.jwt..] - -2/no such file or directory
(97924)[ 0.000] ERROR ziti-sdk:ziti_enroll.c:449 parse_enrollment_jwt() jwt input lacks a second dot
JS ziti_enroll failed with error code (undefined/undefined)
eric@testSdk:~/testSdk$
When I try to enroll the same token with
ziti enroll identity testSdk.jwt
it works fine.
Any idea?
ekoby
February 9, 2026, 1:37pm
14
ziti_enroll take the actual token (not the file name). you just need to read it from the file
something like this:
const fs = require('node:fs');
let jwt_path = process.argv[2];
let jwt = fs.readFileSync(jwt_path, 'utf8');
let data = await ziti_Enroll(jwt)....
Eric
February 10, 2026, 9:51am
15
I made the changes you mentioned and passed the token itself to the function instead of the file name (despite enroll_test on github mentions the file name as param). Unfortunately, now I'm facing this error:
eric@testSdk:~/testSdk$ node testZiti.js testSdk.jwt
Version Ziti : 1.10.9
JS ziti_Enroll() entered
(111245)[ 0.000] ERROR ziti-sdk:ziti_enroll.c:449 parse_enrollment_jwt() jwt input lacks a second dot
JS ziti_enroll failed with error code (undefined/undefined)
eric@testSdk:~/testSdk$
The token is valid. I ckecked it online. Both dots are present. There is no BOM in the file so it's perfect!
Here is the comprehensive code
const fs = require('fs');
const ziti = require('../ziti-sdk-nodejs/build/Release/ziti_sdk_nodejs.node');
const result = ziti.ziti_sdk_version();
console.assert(result, "Échec : version vide");
console.log("Version Ziti :", result);
const ziti_Enroll = async (jwt) => {
console.log("JS ziti_Enroll() entered ")
return new Promise((resolve, reject) => {
let rc = ziti.ziti_enroll(
jwt,
(data) => {
return resolve(data);
}
);
});
};
(async () => {
let jwt_path = process.argv[2];
//let jwt = fs.readFileSync(jwt_path, 'utf8');
let jwt = fs.readFileSync(jwt_path, 'utf8').trim().replace(/\s/g, '');
if (jwt.split('.').length !== 3) {
throw new Error("wrong JWT format: should contain 2 dots");
}
let data = await ziti_Enroll(jwt).catch((data) => {
console.log('JS ziti_enroll failed with error code (%o/%s)', data.status, data.err);
});
if (data && data.identity) {
console.log("data.identity is:\n\n%s\n", data.identity);
}
process.exit(0);
})();
As you can see, I even read the file content to delete potential spaces and also checked that both dots are there.
At last, I wanted to look at ziti_enroll line 449 but there is no line 449.
Please let me know your thoughts because I really don't know how I can move forward.
Eric
February 10, 2026, 12:29pm
16
To complete my previous post, I just tried with debug logs:
eric@testSdk:~/testSdk$ ZITI_LOG=6 node testZiti.js testSdk.jwt
(111652)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=6/TRACE
(111652)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting at (2026-02-10T12:24:43.375)
(111652)[ 0.000] INFO ziti-njs:ziti-add-on.c:47 Init() Ziti NodeJS SDK version 0.24.2@g325edb5(HEAD) starting at (2026-02-10T12:24:43.375)
(111652)[ 0.000] DEBUG ziti-njs:ziti-add-on.c:51 Init()
Version: 0.24.2
Build Date: 2026-02-08T07:08:24Z
Git Branch: main
Git SHA: 6168bcc
OS: Linux
Arch: x86_64
Version Ziti : 1.10.9
JS ziti_Enroll() entered
(111652)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=6/TRACE
(111652)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting at (2026-02-10T12:24:43.393)
(111652)[ 0.000] DEBUG ziti-njs:ziti_enroll.c:160 _ziti_enroll() entered
(111652)[ 0.000] DEBUG ziti-njs:ziti_enroll.c:186 _ziti_enroll() args[1] IS a napi_function
(111652)[ 0.000] INFO ziti-sdk:ziti_enroll.c:112 ziti_enroll() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting enrollment at (2026-02-10T12:24:43.393)
(111652)[ 0.000] VERBOSE ziti-sdk:utils.c:642 load_file() path[eyJhbGciOiJSUzI1..] - -2/no such file or directory
(111652)[ 0.000] ERROR ziti-sdk:ziti_enroll.c:449 parse_enrollment_jwt() jwt input lacks a second dot
JS ziti_enroll failed with error code (undefined/undefined)
eric@testSdk:~/testSdk$
Looking at last lines, i understand that ziti_Enroll function waits for token file name, not the token itself.
After modifying the code to pass the token file name to ziti_Enroll, here is the result:
eric@testSdk:~/testSdk$ ZITI_LOG=6 node testZiti.js testSdk.jwt
(111709)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=6/TRACE
(111709)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting at (2026-02-10T12:32:58.635)
(111709)[ 0.000] INFO ziti-njs:ziti-add-on.c:47 Init() Ziti NodeJS SDK version 0.24.2@g325edb5(HEAD) starting at (2026-02-10T12:32:58.635)
(111709)[ 0.000] DEBUG ziti-njs:ziti-add-on.c:51 Init()
Version: 0.24.2
Build Date: 2026-02-08T07:08:24Z
Git Branch: main
Git SHA: 6168bcc
OS: Linux
Arch: x86_64
Version Ziti : 1.10.9
JS ziti_Enroll() entered
(111709)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=6/TRACE
(111709)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting at (2026-02-10T12:32:58.641)
(111709)[ 0.000] DEBUG ziti-njs:ziti_enroll.c:160 _ziti_enroll() entered
(111709)[ 0.000] DEBUG ziti-njs:ziti_enroll.c:186 _ziti_enroll() args[1] IS a napi_function
(111709)[ 0.000] INFO ziti-sdk:ziti_enroll.c:112 ziti_enroll() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting enrollment at (2026-02-10T12:32:58.641)
(111709)[ 0.000] DEBUG ziti-sdk:ziti_enroll.c:474 parse_enrollment_jwt() jwt signature is: GkGXVWUeLvYby1w9XFdEwyDnhWnEbi_E72_EQSAn5r0ZrEsTKJ2T4rBmXskXOAiug7-PtjnD_6Q15uZvZWqXBH9_kClmP01Fe8avjLlt5q3qkKpyptseFHj_XBqg6OQNxcS-QgwgrfSGt91EJ1cHDQCK7g7MPUcZglnDst4zCzraz4reKq7ezN0c1p6IOH72pw6cTyrUcqcPCWr4Eirs2n_wYPdXhvZBYmF5s9Rd-LGO5HGpGgG3DFMgcdDu5ZmtS5ViSYtJmMYVyxRnO-jy8kenOoF8pkeghaE1lc9hYvq0LE1Kvtq7bqCeYGQfLuuJBCvaN98dJl2zTiGZei2Luch_dhvDMRUFTaPl9_tXkB_tlLi_J9J0bKUovAkBzLqyzQbRUUgstmTl6vXaGC9d5SMnskAmC6enrKglsB76XM0zYQm4jXX1cfJDCV8Qsqh1WbniMV6xFu9i8vXEeq7PQf1ZQj6_Q0E0arkrrA7IcBN3q7hnT4BHxCzq6DKgWYnfYmnufSFojOI0dmssgSqcwdeBq2vtwZOmUrXFAdqjDUsg00_EvAi0ClSApSIf7cvrSTtL95RchLSCK3ivO2SmNhYRwx40rNecOX-3BEvfOIR7Jit90clNDgUpi-qnaLiSgWomTxLRlHZPiUADIM9rMz4F4SACpxTwjncUrjYl3zw
(111709)[ 0.000] INFO ziti-sdk:ziti_ctrl.c:644 ziti_ctrl_init() ctrl[https://zpix.vigitronic.eu:8440] controller initialized
(111709)[ 0.000] DEBUG ziti-sdk:ziti_ctrl.c:655 ziti_ctrl_init() ctrl[https://zpix.vigitronic.eu:8440] ziti controller client initialized
(111709)[ 0.000] VERBOSE ziti-sdk:ziti_ctrl.c:147 start_request() ctrl[https://zpix.vigitronic.eu:8440] starting GET[/version]
(111709)[ 0.000] VERBOSE ziti-sdk:ziti_ctrl.c:147 start_request() ctrl[https://zpix.vigitronic.eu:8440] starting GET[/.well-known/est/cacerts]
JS ziti_enroll failed with error code (undefined/undefined)
eric@testSdk:~/testSdk$
Has someone already used this sdk? Please help!
ekoby
February 10, 2026, 8:46pm
17
I am having trouble reproducing what you're seeing.
First of all, let me correct myself: you can pass either JWT content or a file name into the method, SDK will figure out internally what it needs to do
Second, in your last log it seems that it passed JWT decoding and was in the process of enrollment. Can you check that you can access your controller from the host you're trying to enroll?
simple curl would do curl -k https://zpix.vigitronic.eu:8440
And another thing, you should check the return code from ziti.ziti_enroll, non-zero code would indicate an error
Eric
February 11, 2026, 4:25am
18
Eugene,
Please find below the results:
eric@testSdk:~/testSdk$ curl -k https://zpix.vigitronic.eu:8440
{"data":{"apiVersions":{"edge":{"v1":{"apiBaseUrls":["https://zpix.vigitronic.eu:8440/edge/client/v1"],"path":"/edge/client/v1"}},"edge-client":{"v1":{"apiBaseUrls":["https://zpix.vigitronic.eu:8440/edge/client/v1"],"path":"/edge/client/v1"}},"edge-management":{"v1":{"apiBaseUrls":["https://zpix.vigitronic.eu:8440/edge/management/v1"],"path":"/edge/management/v1"}},"edge-oidc":{"v1":{"apiBaseUrls":["https://zpix.vigitronic.eu:8440"],"path":"/oidc"}},"health-checks":{"v1":{"apiBaseUrls":[],"path":"/health-checks/v1"}}},"buildDate":"2025-07-25T17:21:06Z","capabilities":["OIDC_AUTH"],"revision":"207d28c6bdee","runtimeVersion":"go1.24.1","version":"v1.6.6"},"meta":{}}
eric@testSdk:~/testSdk$
eric@testSdk:~/testSdk$
eric@testSdk:~/testSdk$ ZITI_LOG=6 node testZiti.js testSdk.jwt
(113883)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=6/TRACE
(113883)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting at (2026-02-11T04:21:33.651)
(113883)[ 0.000] INFO ziti-njs:ziti-add-on.c:47 Init() Ziti NodeJS SDK version 0.24.2@g325edb5(HEAD) starting at (2026-02-11T04:21:33.651)
(113883)[ 0.000] DEBUG ziti-njs:ziti-add-on.c:51 Init()
Version: 0.24.2
Build Date: 2026-02-08T07:08:24Z
Git Branch: main
Git SHA: 6168bcc
OS: Linux
Arch: x86_64
Version Ziti : 1.10.9
JS ziti_Enroll() entered
(113883)[ 0.000] INFO ziti-sdk:utils.c:200 ziti_log_set_level() set log level: root=6/TRACE
(113883)[ 0.000] INFO ziti-sdk:utils.c:169 ziti_log_init() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting at (2026-02-11T04:21:33.657)
(113883)[ 0.000] DEBUG ziti-njs:ziti_enroll.c:160 _ziti_enroll() entered
(113883)[ 0.000] DEBUG ziti-njs:ziti_enroll.c:186 _ziti_enroll() args[1] IS a napi_function
(113883)[ 0.000] INFO ziti-sdk:ziti_enroll.c:112 ziti_enroll() Ziti C SDK version 1.10.9 @g325edb5(HEAD) starting enrollment at (2026-02-11T04:21:33.657)
(113883)[ 0.000] DEBUG ziti-sdk:ziti_enroll.c:474 parse_enrollment_jwt() jwt signature is: ptORF1xRxgmgoxAJkLuHHh56PRG2-xxLUJZqB7SzKsyFcSAJG3F9uydlfFoyTzRzv5LSf7evbxRUiqSbRXBpN1UK_E9WXGeKMxS3oKJZ8YB-Z-f2T4oIU9iyYFmRat1uO3KQm5APXTLc0qVgiQFHHu9bQzdHsUU11di-OT-5burOxgZOfgur2TrOVWbDt1uFDnlOAuIb_rnxk2me-dSNmoCaAR1aUMRPrEB7tmuB9UQoQjEVj5odycLebeO6MXpMXKA27Cep1blPNewPVg4cQP9EKsFwkdr_Xzx97fzBcmMMaYtGBB1Eo8yihmLUiFXnyK4O5NOLptSnbYrc3a2YlI0dTKtFHya_ENz3mz54d0y7V3-tRFFn4F8EvBHOG30NRxLkbXqRdefHX8JfxdyGDf8oqEnFicy6miwNHiVBFLAU4qKRt2mQPS-oCWZU_bQSjLMxFTlyS_BfWAbk49IO9jQ7_gkhxi3jbreho4rZbK_m72Td-TcNJRV4cOMGn3RBMHk3YCSavC0I8wzaDTfpF830yakiNiuagxP-XZId4wttIO6wNRxGdYKDyf00r8neGoUtZZMkmsyaiF5Ch5zWHQhFmirtO51SU1fzjtVrzi8633Jop29Ck2HjtbpF2Z2yIbYI2WuftWM7NaeaE5n3kCfHj-Qqof7FeRL1zVAU7e0
(113883)[ 0.000] INFO ziti-sdk:ziti_ctrl.c:644 ziti_ctrl_init() ctrl[https://zpix.vigitronic.eu:8440] controller initialized
(113883)[ 0.000] DEBUG ziti-sdk:ziti_ctrl.c:655 ziti_ctrl_init() ctrl[https://zpix.vigitronic.eu:8440] ziti controller client initialized
(113883)[ 0.000] VERBOSE ziti-sdk:ziti_ctrl.c:147 start_request() ctrl[https://zpix.vigitronic.eu:8440] starting GET[/version]
(113883)[ 0.000] VERBOSE ziti-sdk:ziti_ctrl.c:147 start_request() ctrl[https://zpix.vigitronic.eu:8440] starting GET[/.well-known/est/cacerts]
JS ziti_enroll failed with error code (undefined/undefined)
ziti.ziti_enroll return code: undefined
eric@testSdk:~/testSdk$
I guess the controller answer is ok but the return code frorm ziti.ziti_enroll is undefined.
ekoby
February 11, 2026, 2:55pm
19
did you change your JS code?
I am not sure why you get exception in await ziti_Enroll(jwt).catch((data)... since you do not call reject on the promise
I'd wrap ziti.ziti_enroll() with try { .. } catch to see if any exception is thrown
Eric
February 11, 2026, 3:11pm
20
Eugene,
Is it possible to privately send you a token to test enrollment on your side? This could be a good test.
ekoby
February 11, 2026, 3:15pm
21
absolutely, you can DM me here on discourse