factadesarrolladores API 0.1.0-esqueleto

Descargar el JSON o el PDF

GET /v1/dte/{codigoGeneracion}/file

Alcance: download.

Alcance download

Parámetros

NombreDóndeQué es
X-Facta-Keyrequerido cabecera

La llave de la API. Nunca en Authorization.

codigoGeneracionrequerido ruta

kindopcional consulta

Ejemplos

Solo la primera pestaña usa una librería de Facta; las demás son peticiones HTTP normales, porque no existe un cliente oficial para esos lenguajes.

curl

curl
# -D - vuelca las cabeceras de la respuesta, con RateLimit-Remaining (y, si aplica, Retry-After).
curl --fail-with-body -X GET "$FACTA_API_BASE_URL/v1/dte/7875BC7A-9580-441D-94E4-FA455E9D8BD0/file?kind=json" \
  -H "X-Facta-Key: $FACTA_API_KEY" \
  -D - \
  --max-time 60 \
  -o "archivo.json"

JavaScript

javascript
// JavaScript puro, sin ninguna librería de Facta. Node 22 o 24; fetch es global.
const base = process.env.FACTA_API_BASE_URL;

const headers = {
  "X-Facta-Key": process.env.FACTA_API_KEY,
};

const controlador = new AbortController();
const plazo = setTimeout(() => controlador.abort(), 60_000);

const respuesta = await fetch(base + "/v1/dte/7875BC7A-9580-441D-94E4-FA455E9D8BD0/file?kind=json", {
  method: "GET",
  headers,
  signal: controlador.signal,
}).finally(() => clearTimeout(plazo));

console.log("cupo restante:", respuesta.headers.get("RateLimit-Remaining"));

if (!respuesta.ok) throw new Error(`HTTP ${respuesta.status}`);
// Los bytes exactos: nunca se parsean ni se vuelven a serializar.
const bytes = new Uint8Array(await respuesta.arrayBuffer());
await writeFile("archivo.json", bytes);

TypeScript (SDK)

typescript
import { Facta, FactaError } from "@facta/api";

const facta = new Facta({
  apiKey: process.env.FACTA_API_KEY!,
});

// Los bytes EXACTOS, sin parsear ni volver a serializar.
const archivo = await facta.downloadDocument("7875BC7A-9580-441D-94E4-FA455E9D8BD0", "json");

await writeFile(archivo.filename ?? `${archivo}.json`, archivo.bytes);
console.log(archivo.contentType);   // "application/json"

Python

python
import json
import os
import urllib.error
import urllib.request

base = os.environ["FACTA_API_BASE_URL"]

cabeceras = {
    "X-Facta-Key": os.environ["FACTA_API_KEY"],
}

peticion = urllib.request.Request(
    base + "/v1/dte/7875BC7A-9580-441D-94E4-FA455E9D8BD0/file?kind=json",
    method="GET",
    headers=cabeceras,
)

try:
    # El Ministerio puede tardar unos cuarenta segundos en contestar.
    with urllib.request.urlopen(peticion, timeout=60) as respuesta:
        print("cupo restante:", respuesta.headers.get("RateLimit-Remaining"))
        # Los bytes exactos: nunca se parsean ni se vuelven a serializar.
        datos = respuesta.read()
        with open("archivo.json", "wb") as archivo:
            archivo.write(datos)
except urllib.error.HTTPError as fallo:
    error = json.load(fallo).get("error", {})
    if error.get("code") == "mh_rejected":
        detalle = error.get("details", {})
        print("rechazado:", error.get("message"), "· número gastado:", detalle.get("numeroControl"))
    else:
        raise

PHP

php
<?php
// PHP 8 con la extensión cURL. No hay librería de Facta para PHP.
$base = getenv("FACTA_API_BASE_URL");

$cabeceras = [
    "X-Facta-Key: " . getenv("FACTA_API_KEY"),
];

$conexion = curl_init($base . "/v1/dte/7875BC7A-9580-441D-94E4-FA455E9D8BD0/file?kind=json");
curl_setopt_array($conexion, [
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => true,
    CURLOPT_TIMEOUT => 60,
    CURLOPT_HTTPHEADER => $cabeceras,
]);

$crudo = curl_exec($conexion);
$estado = curl_getinfo($conexion, CURLINFO_RESPONSE_CODE);
$tamCabeceras = curl_getinfo($conexion, CURLINFO_HEADER_SIZE);
curl_close($conexion);

$cabecerasRespuesta = substr($crudo, 0, $tamCabeceras);
echo "cupo restante: " . (preg_match("/RateLimit-Remaining: (\d+)/i", $cabecerasRespuesta, $m) ? $m[1] : "?") . "\n";

// Los bytes exactos: nunca se parsean ni se vuelven a serializar.
$bytes = substr($crudo, $tamCabeceras);
file_put_contents("archivo.json", $bytes);

C#

csharp
// .NET 8 con HttpClient. No hay librería de Facta para C#.
using System.Net.Http.Json;
using System.Text.Json;

var base_ = Environment.GetEnvironmentVariable("FACTA_API_BASE_URL");
using var cliente = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };

var peticion = new HttpRequestMessage(HttpMethod.Get, base_ + "/v1/dte/7875BC7A-9580-441D-94E4-FA455E9D8BD0/file?kind=json");
peticion.Headers.Add("X-Facta-Key", Environment.GetEnvironmentVariable("FACTA_API_KEY"));

var respuesta = await cliente.SendAsync(peticion);
Console.WriteLine($"cupo restante: {respuesta.Headers.GetValues("RateLimit-Remaining").FirstOrDefault()}");

// Los bytes exactos: nunca se parsean ni se vuelven a serializar.
var bytes = await respuesta.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("archivo.json", bytes);

Java

java
// JDK 17 o superior, con java.net.http. No hay librería de Facta para Java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public final class Archivo {
  public static void main(String[] args) throws Exception {
    var base = System.getenv("FACTA_API_BASE_URL");
    var constructor = HttpRequest.newBuilder(URI.create(base + "/v1/dte/7875BC7A-9580-441D-94E4-FA455E9D8BD0/file?kind=json"))
        .header("X-Facta-Key", System.getenv("FACTA_API_KEY"))
        .timeout(Duration.ofSeconds(60))
        .GET();
    var peticion = constructor.build();

    var respuesta = HttpClient.newHttpClient().send(peticion, HttpResponse.BodyHandlers.ofByteArray());
    System.out.println("cupo restante: " + respuesta.headers().firstValue("RateLimit-Remaining").orElse("?"));
    // Los bytes exactos: nunca se parsean ni se vuelven a serializar.
    java.nio.file.Files.write(java.nio.file.Path.of("archivo.json"), respuesta.body());
  }
}

Go

go
// Go 1.22 con net/http. No hay librería de Facta para Go.
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	base := os.Getenv("FACTA_API_BASE_URL")
	peticion, _ := http.NewRequest("GET", base+"/v1/dte/7875BC7A-9580-441D-94E4-FA455E9D8BD0/file?kind=json", nil)
	peticion.Header.Set("X-Facta-Key", os.Getenv("FACTA_API_KEY"))

	cliente := &http.Client{Timeout: 60 * time.Second}
	respuesta, err := cliente.Do(peticion)
	if err != nil {
		panic(err)
	}
	defer respuesta.Body.Close()

	fmt.Println("cupo restante:", respuesta.Header.Get("RateLimit-Remaining"))

	// Los bytes exactos: nunca se parsean ni se vuelven a serializar.
	bytes, _ := io.ReadAll(respuesta.Body)
	os.WriteFile("archivo.json", bytes, 0o600)
}

Qué contesta

200

Los bytes decifrados, tal como se guardaron.

400

El código no tiene forma de UUID.

401

unauthorized (no vino llave) o invalid_api_key (no sirve). Las dos tardan lo mismo, a propósito.

En las rutas que firman se suman las dos del segundo factor: sign_key_required (falta X-Facta-Sign-Key, o vino la de apertura por error) y sign_key_invalid (no abre el vault). details.intentosRestantes dice cuántos quedan antes de que la llave se suspenda sola.

403

La llave existe pero no puede: key_revoked, key_expired, key_inactive, forbidden_scope, dte_type_not_allowed, ip_not_allowed, environment_not_allowed o sign_vault_locked (demasiados intentos fallidos de abrir el vault: esperar no lo arregla, hay que reactivar la llave desde la app).

404

not_found — igual para "ese documento no existe en el área de retención" que para "existe, pero es de otra empresa o de otro ambiente". Nunca 403: distinguir los dos casos confirmaría códigos de generación ajenos, y el código va impreso en cada factura.

429

rate_limited (por hora, por día, o la ventana propia de /v1/status) o amount_limit (el documento pasa del monto máximo de la llave). El de monto se comprueba ANTES de reservar: no gasta correlativo.

details.window dice cuál de las tres fue.

503

service_unavailable o correlative_unavailable — algo nuestro no está listo. Puede salir en CUALQUIER ruta, incluidas las de consulta: el registro de peticiones se escribe antes de trabajar y falla cerrado, así que si esa tabla no contesta, nada contesta.

Escriba para buscar Por ejemplo: idempotencia, contingencia, 422, emitir.

moverse Enter abrir Esc cerrar