Tu primera solicitud
Empieza consultando tu cuenta. Esta solicitud te permite comprobar la llave y la conexión sin timbrar un documento.
Prepara tu cuenta
Sección titulada «Prepara tu cuenta»- Registra tu cuenta de pruebas en sandbox.
- En Configuración → API keys, crea una llave
fc_test_.... Selecciona solo los permisos necesarios y guarda el secreto cuando se muestra. - Consulta
GET /mepara comprobar la conexión.
No necesitas tarjeta, CSD ni activar un plan de pago para esta primera solicitud.
Consulta la cuenta
Sección titulada «Consulta la cuenta»Guarda la llave en la variable de entorno FACTUCAT_API_KEY de tu servidor. Configura la URL de pruebas antes de ejecutar los ejemplos:
export FACTUCAT_API_URL='https://sandbox.factucat.com/api/v1'FACTUCAT_API_URL="${FACTUCAT_API_URL:-https://sandbox.factucat.com/api/v1}"
curl --fail-with-body --request GET \ --url "$FACTUCAT_API_URL/me" \ --header "X-API-Key: $FACTUCAT_API_KEY"const baseUrl = process.env.FACTUCAT_API_URL || "https://sandbox.factucat.com/api/v1"const response = await fetch(`${baseUrl}/me`, { method: "GET", headers: { 'X-API-Key': process.env.FACTUCAT_API_KEY, },})const text = await response.text()if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`)console.log(JSON.parse(text))import jsonimport osimport urllib.errorimport urllib.request
base_url = os.environ.get("FACTUCAT_API_URL", "https://sandbox.factucat.com/api/v1")request = urllib.request.Request( f"{base_url}/me", method="GET", headers={ "X-API-Key": os.environ["FACTUCAT_API_KEY"], },)try: with urllib.request.urlopen(request) as response: text = response.read().decode("utf-8")except urllib.error.HTTPError as error: raise RuntimeError(f"HTTP {error.code}: {error.read().decode('utf-8')}") from errorprint(json.loads(text))package main
import ( "fmt" "io" "net/http" "os")
func main() { baseURL := os.Getenv("FACTUCAT_API_URL") if baseURL == "" { baseURL = "https://sandbox.factucat.com/api/v1" } request, err := http.NewRequest("GET", baseURL + "/me", nil) if err != nil { panic(err) } request.Header.Set("X-API-Key", os.Getenv("FACTUCAT_API_KEY")) response, err := http.DefaultClient.Do(request) if err != nil { panic(err) } defer response.Body.Close() text, _ := io.ReadAll(response.Body) if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d: %s", response.StatusCode, text)) } fmt.Print(string(text))}La respuesta exitosa tiene un objeto data con la cuenta y los permisos de la llave, y meta.requestId para identificar la solicitud.
Si recibes 401, comprueba la llave, su revocación y el ambiente. Si recibes 403, revisa los permisos. Consulta los errores.
Crea un borrador
Sección titulada «Crea un borrador»Con el permiso invoice:write, puedes crear un borrador vacío. Todavía no se emite un CFDI ni se genera consumo.
FACTUCAT_API_URL="${FACTUCAT_API_URL:-https://sandbox.factucat.com/api/v1}"
curl --fail-with-body --request POST \ --url "$FACTUCAT_API_URL/invoice-drafts" \ --header "X-API-Key: $FACTUCAT_API_KEY" \ --header 'Content-Type: application/json' \ --data '{}'const baseUrl = process.env.FACTUCAT_API_URL || "https://sandbox.factucat.com/api/v1"const body = JSON.stringify({})const response = await fetch(`${baseUrl}/invoice-drafts`, { method: "POST", headers: { 'X-API-Key': process.env.FACTUCAT_API_KEY, 'Content-Type': 'application/json', }, body,})const text = await response.text()if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`)console.log(JSON.parse(text))import jsonimport osimport urllib.errorimport urllib.request
base_url = os.environ.get("FACTUCAT_API_URL", "https://sandbox.factucat.com/api/v1")body = "{}".encode("utf-8")request = urllib.request.Request( f"{base_url}/invoice-drafts", method="POST", headers={ "X-API-Key": os.environ["FACTUCAT_API_KEY"], "Content-Type": "application/json", }, data=body,)try: with urllib.request.urlopen(request) as response: text = response.read().decode("utf-8")except urllib.error.HTTPError as error: raise RuntimeError(f"HTTP {error.code}: {error.read().decode('utf-8')}") from errorprint(json.loads(text))package main
import ( "bytes" "fmt" "io" "net/http" "os")
func main() { baseURL := os.Getenv("FACTUCAT_API_URL") if baseURL == "" { baseURL = "https://sandbox.factucat.com/api/v1" } body := []byte("{}") request, err := http.NewRequest("POST", baseURL + "/invoice-drafts", bytes.NewReader(body)) if err != nil { panic(err) } request.Header.Set("X-API-Key", os.Getenv("FACTUCAT_API_KEY")) request.Header.Set("Content-Type", "application/json") response, err := http.DefaultClient.Do(request) if err != nil { panic(err) } defer response.Body.Close() text, _ := io.ReadAll(response.Body) if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d: %s", response.StatusCode, text)) } fmt.Print(string(text))}Guarda data.id junto a tu pedido. Idempotency-Key es opcional: si la añades, reutilízala solo para repetir la misma solicitud. Idempotencia y reintentos.
Este borrador vacío sirve para comprobar una primera escritura. El tutorial Cómo timbrar un CFDI completo comienza una venta nueva, crea al cliente y lo vincula desde el alta del borrador. Si continúas con el borrador vacío que acabas de crear, conserva su ID y completa explícitamente sus campos receiver* con PATCH /invoice-drafts/{id}/receiver antes de agregar los conceptos.
Pasa a producción
Sección titulada «Pasa a producción»Para emitir CFDI reales, usa una cuenta en FactuCat con datos fiscales, CSD vigente y un plan con acceso a la API (FactuCat API o Miau Pro). El plan API pide una tarjeta y tu aceptación del cobro por consumo. Cada ambiente tiene su propia cuenta, URL y llave: sigue Ambientes y pruebas.