// documentación

Chest Vars
API Reference

Almacená y leé variables de configuración desde cualquier lenguaje o plataforma. Sin archivos .env, sin configuraciones locales.

Base URL https://chestvars.com/api
Overview .

Chest Vars es un sistema de gestión centralizada de variables de configuración. En lugar de mantener archivos .env en cada servidor, guardás tus variables en Chest Vars y las leés con un simple HTTP request.

Cómo funciona

Cada proyecto tiene un owner (nombre de usuario del propietario) y un nombre de proyecto. Las variables se identifican por clave y están protegidas por alguno de los tres métodos de autenticación disponibles.

Tip: Podés leer múltiples variables en un solo request para minimizar la latencia. Ver Múltiples variables.
Autenticación .

Cada proyecto usa uno de tres métodos de autenticación. El método se configura al crear el proyecto en el panel web.

password
Usuario y contraseña del proyecto
bearer
Token en header Authorization
jwt
JSON Web Token firmado

Bearer Token (recomendado)

Incluir en el header de cada request:

HTTP
Authorization: Bearer tu_token_aqui

Password

Incluir en el body del request:

JSON
{
  "owner":    "nombre_usuario",
  "project":  "nombre_proyecto",
  "user":     "usuario_proyecto",
  "password": "contraseña_proyecto"
}

JWT (HS256)

Firmar un JWT con el secret del proyecto e incluirlo como Bearer token:

JWT Payload
{
  "owner":   "nombre_usuario",
  "project": "nombre_proyecto",
  "iat":     1700000000
}
Errores .

Todas las respuestas incluyen un campo ok. Si es false, el campo error contiene la descripción.

JSON
{ "ok": false, "error": "Token inválido o expirado" }
CódigoSignificado
200OK — request exitoso
400Bad Request — faltan campos o son inválidos
401Unauthorized — credenciales incorrectas
403Forbidden — sin permisos para esta operación
404Not Found — proyecto o variable no encontrada
429Too Many Requests — rate limit excedido
500Server Error — error interno
Leer variables .
POST /api/get Leer una o más variables

Lee variables de un proyecto. Si se omite keys, devuelve todas las variables del proyecto.

Body

CampoTipoDescripción
ownerstringrequeridoUsername del dueño del proyecto
projectstringrequeridoNombre del proyecto
keysarrayopcionalLista de claves a leer. Sin este campo devuelve todas.

Request

JSON
{
  "owner":   "miusuario",
  "project": "mi-proyecto",
  "keys":    ["DB_HOST", "DB_PORT", "API_KEY"]
}

Response

JSON
{
  "ok": true,
  "data": {
    "DB_HOST":  "db.example.com",
    "DB_PORT":  "5432",
    "API_KEY":  "sk-abc123..."
  }
}
Escribir variables .
Permiso requerido: El colaborador debe tener permiso de escritura en el proyecto.
POST /api/set Crear o actualizar variables

Crea o actualiza una o más variables. Si la clave ya existe, actualiza el valor.

Body

CampoTipoDescripción
ownerstringrequeridoUsername del dueño del proyecto
projectstringrequeridoNombre del proyecto
dataobjectrequeridoObjeto clave:valor con las variables a guardar

Request

JSON
{
  "owner":   "miusuario",
  "project": "mi-proyecto",
  "data": {
    "DB_HOST":  "nuevo-host.com",
    "VERSION":  "2.1.0"
  }
}

Response

JSON
{
  "ok":      true,
  "updated": ["DB_HOST", "VERSION"],
  "errors":  {}
}
PHP .
PHP
<?php
// Chest Vars – Cliente PHP
// Requiere PHP 7.4+ con extensión curl o allow_url_fopen=On

function chestvars_get(
    string $owner,
    string $project,
    string $token,
    array  $keys = []
): array {
    $body = ['owner' => $owner, 'project' => $project];
    if (!empty($keys)) $body['keys'] = $keys;

    $ch = curl_init('https://chestvars.com/api/get');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode($body),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $token,
        ],
    ]);

    $response = curl_exec($ch);
    $error    = curl_error($ch);
    curl_close($ch);

    if ($error) throw new RuntimeException('cURL error: ' . $error);

    $data = json_decode($response, true);
    if (!$data['ok']) throw new RuntimeException($data['error'] ?? 'Error desconocido');

    return $data['data'];
}

// Uso
$vars = chestvars_get(
    owner:   'miusuario',
    project: 'mi-proyecto',
    token:   'mi_bearer_token',
    keys:    ['DB_HOST', 'DB_PORT', 'REDIS_URL']
);

echo $vars['DB_HOST'];   // db.example.com
echo $vars['DB_PORT'];   // 5432
Python .
Python
import requests

def chestvars_get(owner, project, token, keys=None):
    """Lee variables de Chest Vars."""
    body = {"owner": owner, "project": project}
    if keys:
        body["keys"] = keys

    response = requests.post(
        "https://chestvars.com/api/get",
        json=body,
        headers={"Authorization": f"Bearer {token}"},
        timeout=10,
    )
    response.raise_for_status()
    data = response.json()

    if not data.get("ok"):
        raise ValueError(data.get("error", "Error desconocido"))

    return data["data"]


def chestvars_set(owner, project, token, variables: dict):
    """Guarda variables en Chest Vars."""
    response = requests.post(
        "https://chestvars.com/api/set",
        json={"owner": owner, "project": project, "data": variables},
        headers={"Authorization": f"Bearer {token}"},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()


# Uso
TOKEN   = "mi_bearer_token"
OWNER   = "miusuario"
PROJECT = "mi-proyecto"

vars = chestvars_get(OWNER, PROJECT, TOKEN, keys=["DB_HOST", "API_KEY"])
print(vars["DB_HOST"])   # db.example.com

# Guardar
chestvars_set(OWNER, PROJECT, TOKEN, {"VERSION": "3.0.0"})
JavaScript / Node.js .
JavaScript
const CHESTVARS = {
  BASE_URL: 'https://chestvars.com/api',
  TOKEN:    'mi_bearer_token',
  OWNER:    'miusuario',
  PROJECT:  'mi-proyecto',
};

async function cvGet(keys = []) {
  const body = {
    owner:   CHESTVARS.OWNER,
    project: CHESTVARS.PROJECT,
    ...(keys.length && { keys }),
  };

  const res = await fetch(`${CHESTVARS.BASE_URL}/get`, {
    method:  'POST',
    headers: {
      'Content-Type':  'application/json',
      'Authorization': `Bearer ${CHESTVARS.TOKEN}`,
    },
    body: JSON.stringify(body),
  });

  const data = await res.json();
  if (!data.ok) throw new Error(data.error);
  return data.data;
}

async function cvSet(variables) {
  const res = await fetch(`${CHESTVARS.BASE_URL}/set`, {
    method:  'POST',
    headers: {
      'Content-Type':  'application/json',
      'Authorization': `Bearer ${CHESTVARS.TOKEN}`,
    },
    body: JSON.stringify({
      owner:   CHESTVARS.OWNER,
      project: CHESTVARS.PROJECT,
      data:    variables,
    }),
  });

  const data = await res.json();
  if (!data.ok) throw new Error(data.error);
  return data;
}

// Uso
const vars = await cvGet(['DB_HOST', 'STRIPE_KEY']);
console.log(vars.DB_HOST);

await cvSet({ VERSION: '2.0.0', DEPLOYED_AT: new Date().toISOString() });
JavaScript (axios)
// npm install axios
const axios = require('axios');

const client = axios.create({
  baseURL: 'https://chestvars.com/api',
  headers: { Authorization: 'Bearer mi_bearer_token' },
  timeout: 10000,
});

async function cvGet(owner, project, keys = []) {
  const { data } = await client.post('/get.php', {
    owner, project, ...(keys.length && { keys }),
  });
  if (!data.ok) throw new Error(data.error);
  return data.data;
}

// Uso
const vars = await cvGet('miusuario', 'mi-proyecto', ['DB_HOST']);
console.log(vars.DB_HOST);
VB.NET .
VB.NET
Imports System.Net.Http
Imports System.Text
Imports System.Text.Json

' Requiere .NET 5+ o .NET Framework 4.5+ con Newtonsoft.Json
Module ChestVarsClient

    Private Const BaseUrl  As String = "https://chestvars.com/api"
    Private Const Token    As String = "mi_bearer_token"
    Private Const Owner    As String = "miusuario"
    Private Const Project  As String = "mi-proyecto"

    ''' <summary>
    ''' Lee variables de Chest Vars.
    ''' </summary>
    Public Async Function GetVariables(
        ParamArray keys() As String
    ) As Task(Of Dictionary(Of String, String))

        Using client As New HttpClient()
            client.DefaultRequestHeaders.Add("Authorization", "Bearer " & Token)

            Dim body As New Dictionary(Of String, Object) From {
                {"owner",   Owner},
                {"project", Project}
            }
            If keys.Length > 0 Then body.Add("keys", keys)

            Dim json     = JsonSerializer.Serialize(body)
            Dim content  = New StringContent(json, Encoding.UTF8, "application/json")
            Dim response = Await client.PostAsync(BaseUrl & "/get", content)
            Dim raw      = Await response.Content.ReadAsStringAsync()

            Dim result   = JsonSerializer.Deserialize(Of ApiResponse)(raw)
            If Not result.ok Then Throw New Exception(result.error)

            Return result.data
        End Using
    End Function

    ''' <summary>
    ''' Guarda variables en Chest Vars.
    ''' </summary>
    Public Async Function SetVariables(
        variables As Dictionary(Of String, String)
    ) As Task

        Using client As New HttpClient()
            client.DefaultRequestHeaders.Add("Authorization", "Bearer " & Token)

            Dim body As New Dictionary(Of String, Object) From {
                {"owner",   Owner},
                {"project", Project},
                {"data",    variables}
            }

            Dim json     = JsonSerializer.Serialize(body)
            Dim content  = New StringContent(json, Encoding.UTF8, "application/json")
            Dim response = Await client.PostAsync(BaseUrl & "/set", content)
            Dim raw      = Await response.Content.ReadAsStringAsync()

            Dim result   = JsonSerializer.Deserialize(Of ApiResponse)(raw)
            If Not result.ok Then Throw New Exception(result.error)
        End Using
    End Function

    Private Class ApiResponse
        Public Property ok    As Boolean
        Public Property data  As Dictionary(Of String, String)
        Public Property [error] As String
    End Class

End Module

' ── Uso desde un Form ──────────────────────────────────────
' Dim vars = Await ChestVarsClient.GetVariables("DB_HOST", "API_KEY")
' TextBox1.Text = vars("DB_HOST")
'
' Await ChestVarsClient.SetVariables(New Dictionary(Of String, String) From {
'     {"VERSION", "2.0.0"}
' })
cURL .

Leer variables

Bash
curl -X POST https://chestvars.com/api/get \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer mi_bearer_token" \
  -d '{
    "owner":   "miusuario",
    "project": "mi-proyecto",
    "keys":    ["DB_HOST", "DB_PORT"]
  }'

Escribir variables

Bash
curl -X POST https://chestvars.com/api/set \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer mi_bearer_token" \
  -d '{
    "owner":   "miusuario",
    "project": "mi-proyecto",
    "data": {
      "VERSION":    "2.1.0",
      "UPDATED_AT": "2025-01-01T00:00:00Z"
    }
  }'
C# .
C#
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;

public class ChestVarsClient
{
    private readonly HttpClient _http;
    private readonly string _owner;
    private readonly string _project;

    public ChestVarsClient(string token, string owner, string project)
    {
        _owner   = owner;
        _project = project;
        _http    = new HttpClient
        {
            BaseAddress = new Uri("https://chestvars.com/api/")
        };
        _http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
    }

    public async Task<Dictionary<string, string>> GetAsync(params string[] keys)
    {
        var body = new { owner = _owner, project = _project, keys };
        var res  = await _http.PostAsJsonAsync("get", body);
        var data = await res.Content.ReadFromJsonAsync<ApiResponse>();

        if (!data!.Ok)
            throw new Exception(data.Error ?? "Error desconocido");

        return data.Data ?? new();
    }

    public async Task SetAsync(Dictionary<string, string> variables)
    {
        var body = new { owner = _owner, project = _project, data = variables };
        var res  = await _http.PostAsJsonAsync("set", body);
        var data = await res.Content.ReadFromJsonAsync<ApiResponse>();

        if (!data!.Ok)
            throw new Exception(data.Error ?? "Error desconocido");
    }

    private record ApiResponse(
        bool Ok,
        Dictionary<string, string>? Data,
        string? Error
    );
}

// Uso
var cv   = new ChestVarsClient("mi_token", "miusuario", "mi-proyecto");
var vars = await cv.GetAsync("DB_HOST", "REDIS_URL");

Console.WriteLine(vars["DB_HOST"]);

await cv.SetAsync(new() { ["VERSION"] = "3.0.0" });
ESP32 / Arduino .

El ESP32 puede conectarse a Chest Vars via HTTPS usando la librería HTTPClient incluida en el Arduino ESP32 core. Requiere conexión WiFi y un certificado raíz para validar TLS.

Librería requerida: ArduinoJson — instalable desde el Library Manager de Arduino IDE buscando "ArduinoJson" de Benoit Blanchon. Versión 6 o superior.

Leer variables al iniciar

C++ (Arduino / ESP32)
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// ── Configuración ──────────────────────────────────────────
const char* WIFI_SSID     = "tu_red_wifi";
const char* WIFI_PASSWORD = "tu_clave_wifi";

const char* CV_TOKEN   = "tu_bearer_token";
const char* CV_OWNER   = "miusuario";
const char* CV_PROJECT = "mi-proyecto";
const char* CV_URL     = "https://chestvars.com/api/get";

// Variables leídas de Chest Vars
String dbHost;
String apiKey;

// ── Certificado raíz de chestvars.com ─────────────────────
// Obtenerlo con: openssl s_client -connect chestvars.com:443 -showcerts
// Pegá el certificado raíz (último de la cadena) aquí
const char* ROOT_CA = R"(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
... (pegá el certificado completo aquí) ...
-----END CERTIFICATE-----
)";

// ── Función principal: leer variables ─────────────────────
bool chestvars_get(const char* keys[], int keyCount) {
    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("[ChestVars] Sin WiFi");
        return false;
    }

    // Armar el JSON del body
    StaticJsonDocument<256> doc;
    doc["owner"]   = CV_OWNER;
    doc["project"] = CV_PROJECT;

    JsonArray keysArr = doc.createNestedArray("keys");
    for (int i = 0; i < keyCount; i++) {
        keysArr.add(keys[i]);
    }

    String body;
    serializeJson(doc, body);

    // Hacer el request
    HTTPClient http;
    http.begin(CV_URL, ROOT_CA);
    http.addHeader("Content-Type", "application/json");
    http.addHeader("Authorization", String("Bearer ") + CV_TOKEN);

    int httpCode = http.POST(body);

    if (httpCode != 200) {
        Serial.printf("[ChestVars] Error HTTP: %d\n", httpCode);
        http.end();
        return false;
    }

    // Parsear respuesta
    String response = http.getString();
    http.end();

    StaticJsonDocument<1024> resp;
    DeserializationError error = deserializeJson(resp, response);

    if (error || !resp["ok"].as<bool>()) {
        Serial.println("[ChestVars] Error en respuesta");
        Serial.println(response);
        return false;
    }

    // Leer los valores
    JsonObject data = resp["data"];
    dbHost = data["DB_HOST"].as<String>();
    apiKey = data["API_KEY"].as<String>();

    Serial.println("[ChestVars] Variables cargadas OK");
    Serial.printf("  DB_HOST: %s\n", dbHost.c_str());
    return true;
}

// ── Setup ─────────────────────────────────────────────────
void setup() {
    Serial.begin(115200);

    // Conectar WiFi
    Serial.printf("Conectando a %s...\n", WIFI_SSID);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nWiFi conectado: " + WiFi.localIP().toString());

    // Leer variables de Chest Vars
    const char* keys[] = {"DB_HOST", "API_KEY"};
    if (!chestvars_get(keys, 2)) {
        Serial.println("Error al leer configuración. Reiniciando...");
        delay(3000);
        ESP.restart();
    }

    // Usar los valores
    Serial.println("Config cargada. Arrancando app...");
}

void loop() {
    // Tu código aquí — dbHost y apiKey ya están disponibles
}

Sin validación TLS (desarrollo)

Si estás probando en red local o no tenés el certificado, podés deshabilitar la validación TLS. No usar en producción.

C++ — sin TLS (solo desarrollo)
// Reemplazar http.begin(CV_URL, ROOT_CA) por:
http.begin(CV_URL);
http.setInsecure();  // ← deshabilita verificación SSL

Obtener el certificado raíz

Para obtener el certificado correcto, ejecutá este comando en tu terminal y copiá el último certificado de la cadena (el raíz):

Bash
openssl s_client -connect chestvars.com:443 -showcerts 2>/dev/null \
  | awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' \
  | tail -n +$(grep -c "BEGIN CERTIFICATE" <(openssl s_client \
      -connect chestvars.com:443 -showcerts 2>/dev/null))
Tip de memoria: El ESP32 tiene RAM limitada. Usá StaticJsonDocument con el tamaño justo para tu respuesta. Si tenés muchas variables, aumentá el tamaño o leelas en lotes. Podés calcular el tamaño exacto en arduinojson.org/v6/assistant.
Flutter / Dart .

Integración completa para apps Flutter. Incluye lectura, escritura y un cliente reutilizable con soporte de Bearer token.

Dependencia requerida: http: ^1.2.0 y characters: ^1.3.0 — agregá ambas a tu pubspec.yaml y corré flutter pub get.

Cliente reutilizable

Dart
import 'dart:convert';
import 'package:characters/characters.dart';
import 'package:http/http.dart' as http;

class ChestVars {
  static const String _base    = 'https://chestvars.com/api';
  static const String _owner   = 'miusuario';
  static const String _project = 'mi-proyecto';
  static const String _token   = 'mi_bearer_token';

  static Map<String, String> get _headers => {
    'Content-Type':  'application/json',
    'Authorization': 'Bearer $_token',
  };

  // ── Leer variables ────────────────────────────────────────
  static Future<Map<String, String>> get({List<String>? keys}) async {
    final body = <String, dynamic>{
      'owner':   _owner,
      'project': _project,
      if (keys != null) 'keys': keys,
    };

    final res = await http.post(
      Uri.parse('$_base/get'),
      headers: _headers,
      body: jsonEncode(body),
    );

    final data = jsonDecode(res.body) as Map<String, dynamic>;
    if (data['ok'] != true) throw Exception(data['error']);

    return Map<String, String>.from(data['data'] as Map);
  }

  // ── Guardar variables ─────────────────────────────────────
  static Future<void> set(Map<String, String> variables) async {
    // Enmascarar valores: reverse unicode-safe + base64
    final encoded = variables.map((k, v) {
      final reversed = v.characters.toList().reversed.join();
      final masked   = base64Encode(utf8.encode(reversed));
      return MapEntry(k, masked);
    });

    final body = {
      'owner':   _owner,
      'project': _project,
      'encoded': true,
      'data':    encoded,
    };

    final res = await http.post(
      Uri.parse('$_base/set'),
      headers: _headers,
      body: jsonEncode(body),
    );

    final data = jsonDecode(res.body) as Map<String, dynamic>;
    if (data['ok'] != true) throw Exception(data['error']);
  }
}

Leer variables al iniciar la app

Dart
// En main() o en initState() de tu widget inicial
Future<void> _loadConfig() async {
  try {
    final vars = await ChestVars.get(keys: ['API_URL', 'FEATURE_FLAGS', 'TIMEOUT']);

    final apiUrl      = vars['API_URL']      ?? 'https://default.example.com';
    final timeout     = int.tryParse(vars['TIMEOUT'] ?? '30') ?? 30;
    final flagsJson   = vars['FEATURE_FLAGS'] ?? '{}';

    print('Config cargada: $apiUrl, timeout: ${timeout}s');
  } catch (e) {
    print('Error al cargar config: $e');
    // Usar valores por defecto si falla
  }
}

Guardar variables desde la app

Dart
// Guardar múltiples variables de una vez
await ChestVars.set({
  'LAST_SYNC':    DateTime.now().toIso8601String(),
  'APP_VERSION':  '2.1.0',
  'USER_PREFS':   jsonEncode({'theme': 'dark', 'lang': 'es'}),
  'EMOJI_TEST':   '🎉 Funciona con emojis',
});
Tip: Cacheá las variables en memoria al iniciar la app — no hagas un request a Chest vars en cada build del widget. Usá un Provider o InheritedWidget para distribuir la config por toda la app.
Múltiples variables .

Siempre que necesites más de una variable, leelas todas en un único request. Esto reduce la latencia significativamente comparado con requests individuales.

JSON — un request, todas las variables
// ✅ Recomendado — un solo request
{
  "owner":   "miusuario",
  "project": "mi-proyecto",
  "keys":    ["DB_HOST", "DB_PORT", "DB_NAME", "DB_USER", "DB_PASS"]
}

// ❌ Evitar — múltiples requests innecesarios
// GET DB_HOST → request 1
// GET DB_PORT → request 2
// GET DB_NAME → request 3
// ...

Omitir el campo keys devuelve todas las variables del proyecto de una sola vez.

Rate limiting .

Para proteger el servicio, existe un límite de requests por IP y por proyecto. Si superás el límite, la API devuelve HTTP 429 con el error correspondiente.

Buena práctica: Cacheá las variables en tu aplicación y leelas de Chest Vars solo al iniciar el proceso, no en cada request de tu app.
Python — ejemplo con caché
import requests
from functools import lru_cache

@lru_cache(maxsize=1)
def get_config():
    """Lee la config una sola vez y la cachea en memoria."""
    return chestvars_get(OWNER, PROJECT, TOKEN)

# Al iniciar la app:
config = get_config()
db_host = config["DB_HOST"]  # no hace un request nuevo