Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tutosrive/factus_challenge/llms.txt

Use this file to discover all available pages before exploring further.

The Factus API is protected by OAuth 2.0. The src/auth/token.js module handles the complete token lifecycle — initial acquisition, storage, and periodic rotation — so that the rest of the application only ever needs to read process.env.access_token. When main.js boots, it calls token() immediately and sets up an automatic refresh interval, ensuring a valid token is always available before the first HTTP request is made.

Grant Flows

The module decides which grant type to use by checking whether process.env.refresh_token is already populated at call time.

Password Grant

Used on first run (or after a server restart) when no refresh_token is present in the environment.Sends the following fields to POST /oauth/token:
  • client_id
  • client_secret
  • grant_type=password
  • username (the value of process.env.email)
  • password

Refresh Token Grant

Used on all subsequent calls once a refresh_token has been stored in process.env.Sends the following fields to POST /oauth/token:
  • client_id
  • client_secret
  • grant_type=refresh_token
  • refresh_token

Token Endpoint

All token requests are sent as POST to:
${url_api}/oauth/token
The request body is serialized as application/x-www-form-urlencoded using qs.stringify — not JSON — as required by the OAuth 2.0 specification for the token endpoint.
const config = {
  method: 'post',
  url: `${process.env.url_api}/oauth/token`,
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  data: stringify(data),
};

Automatic Token Refresh

After the initial token acquisition, token.js schedules a setInterval that re-runs the query() function every 55 minutes (3,300,000 ms). Because the refresh_token is available in process.env by then, every scheduled call uses the refresh token grant — no credentials are re-submitted after the first run.
id_interval = setInterval(() => {
  query();
}, 3300000); // cada 55 minutos se genera un nuevo token

// 60000  -> 1 minuto
// 3300000 -> 55 minutos
The interval ID is stored in the module-level variable id_interval and returned by the exported token() function, making it possible to cancel the refresh cycle if needed.

Token Storage

After each successful response from the token endpoint, both tokens are written directly into the Node.js process environment:
process.env.refresh_token = res.data.refresh_token;
process.env.access_token = res.data.access_token;
No file is written to disk. Tokens exist only in the running process’s memory.
Because tokens are stored in memory only, a server restart clears both tokens. On the next startup token.js will find no refresh_token in process.env and fall back to the password grant, requiring valid email and password credentials in your .env file.

Using the Access Token in API Requests

Every outgoing request to the Factus API attaches the in-memory token as a Bearer credential:
headers: {
  Accept: 'application/json',
  Authorization: `Bearer ${process.env.access_token}`,
}
This pattern is used consistently throughout src/controllers/factura.controller.js inside the request_fact() helper.

API Base URL

Use https://api-sandbox.factus.com.co as url_api during development and testing. Switch to https://api.factus.com.co when deploying to production. The only change required is the value of the url_api environment variable — no code changes are needed.

Required Environment Variables

VariableRequiredDescription
url_api✅ YesFactus API base URL (sandbox or production)
client_id✅ YesOAuth 2.0 client identifier issued by Factus
client_secret✅ YesOAuth 2.0 client secret issued by Factus
email✅ YesFactus account email — used as username in password grant
password✅ YesFactus account password — used in password grant
refresh_token⬜ OptionalLeave blank on first run; written automatically at runtime

src/auth/token.js — Full Source

src/auth/token.js
import axios from 'axios';
import { stringify } from 'qs';

let id_interval;

/**
 * Generar Token cada 55 minutos
 */
export default async function token() {
  const query = async () => {
    const data = {
      client_id: process.env.client_id,
      client_secret: process.env.client_secret,
    };

    // Ya existe un refresh token?
    if (process.env.refresh_token) {
      console.log('SÍ existe un refresh token');
      data.grant_type = 'refresh_token';
      data.refresh_token = process.env.refresh_token;
    } else {
      console.log('NO existe un refresh token');
      data.grant_type = 'password';
      data.password = process.env.password;
      data.username = process.env.email;
    }

    // Configuración de la solicitud
    const config = {
      method: 'post',
      url: `${process.env.url_api}/oauth/token`,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      data: stringify(data),
    };
    try {
      const res = await axios(config);
      process.env.refresh_token = res.data.refresh_token;
      process.env.access_token = res.data.access_token;
    } catch (e) {
      console.log(e);
    }
  };
  await query();
  id_interval = setInterval(() => {
    query();
  }, 3300000); // cada 55 minutos se genera un nuevo token
  return id_interval;
}

// 60000 -> 1 minuto
// 3300000 -> 55 minutos

Build docs developers (and LLMs) love