Node.js ziti.init returns code -13

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.

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.

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,

Eugene,

Following previous message, I have upgraded ziti-sdk-nodejs to 0.24.1 and got same result.

Best regards,

please enable logging

wait.. where did you get this identity file from? It is not in the correct format

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?

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@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$

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.

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

Sure, I'll try to use this code. I don't remember where I found this identity file description.

Thanks a lot.

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?

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)....

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.

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. :thinking:

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! :anxious_face_with_sweat:

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

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.

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

Eugene,

Is it possible to privately send you a token to test enrollment on your side? This could be a good test.

absolutely, you can DM me here on discourse