AlchemyCode API docs
SAT · sat.domicilio

Estatus de Domicilio Fiscal

Consulta en el portal del SAT la pestaña de ubicación fiscal y devuelve el estatus del domicilio, la fecha de la consulta y la evidencia de pantalla. No genera PDF oficial.

Asíncrono · 202 + job 1 unidad por consulta Sin costo en beta · consumo medido Requiere: e.firma (.cer + .key + contraseña)

Qué hace

Verifica que el domicilio fiscal declarado siga vigente en el padrón. Este servicio no produce un PDF oficial: devuelve el estatus y una evidencia de pantalla, útil como señal de riesgo en expedientes de crédito o arrendamiento.

Qué obtienes:

  • Estatus del domicilio y código interno de estatus.
  • Domicilio declarado según el portal del SAT.
  • Fecha de la última verificación y evidencia en imagen.

Requisitos

RequisitoQué necesitaDónde se configura
fiele.firma vigente del RFC de la empresa.Se carga en POST /v1/companies/{id}/credentials/sat.
Si falta un requisito, el job termina con CREDENTIAL_MISSING y el detalle indica exactamente qué cargar. Consulta Empresas y credenciales.
La empresa debe estar activa. Si está archivada o suspendida, la API responde COMPANY_SUSPENDED antes de crear el job.
La latencia declarada en esta página es orientativa: 15 – 40 s típico. El portal de origen puede tardar más en horas pico; tu integración debe tolerar reintentos y jobs lentos.

Petición

POST/v1/sat/domiciliosAPI key con scope services:run
CampoTipoDescripción
company_idUUIDrequeridoEmpresa con e.firma vigente.
waitbooleanopcionalEspera hasta 25 s el resultado. Por omisión false.
Cada consulta consume 1 unidad de tu cuota mensual, se cobre o no. El detalle del cálculo está en Límites y cuotas.

Este endpoint acepta Idempotency-Key: si reintentas con la misma clave y el mismo cuerpo, recibes el mismo job en lugar de duplicar la consulta. La clave se conserva 24 horas.

Respuesta

Si wait es false (por omisión), la respuesta es 202 Accepted con el job en cola. El resultado se lee después con GET /v1/jobs/{id} o llega por webhook.

JSON
{
  "object": "job",
  "id": "4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08",
  "service": "sat.domicilio",
  "status": "queued",
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "created_at": "2026-10-31T16:42:07.318920+00:00",
  "started_at": null,
  "finished_at": null,
  "units": 0,
  "result": null,
  "files": [],
  "error": null,
  "warnings": [],
  "sandbox": false,
  "links": {
    "self": "/v1/jobs/4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08"
  }
}

Cuando el job termina, el objeto cambia a succeeded y trae result y files. Este es un ejemplo realista:

JSON
{
  "object": "job",
  "id": "4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08",
  "service": "sat.domicilio",
  "status": "succeeded",
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "created_at": "2026-10-31T16:42:07.318920+00:00",
  "started_at": "2026-10-31T16:42:08.041228+00:00",
  "finished_at": "2026-10-31T16:44:12.882014+00:00",
  "units": 1,
  "result": {
    "rfc": "OMX2503255F9",
    "razon_social": "OMNICONMX, S.A. de C.V.",
    "estatus": "Verificado",
    "codigo_estatus": "DOM_VERIFICADO",
    "domicilio_declarado": {
      "calle": "Av. Insurgentes Sur",
      "colonia": "Crédito Constructor",
      "codigo_postal": "03940",
      "municipio": "Benito Juárez",
      "estado": "Ciudad de México"
    },
    "ultima_verificacion": "2026-10-18",
    "advertencia": null
  },
  "files": [
    {
      "id": "a91c4f7e2b6d48c05e3a1f9b7d2c6084",
      "name": "evidencia-domicilio.png",
      "content_type": "image/png",
      "kind": "evidence",
      "size": 96256,
      "sha256": "4a7d2c9e1f6b8305d2c4a7e9f1b3d5c8a0e2f4b6d8c1a3e5f7b9d0c2e4a6f8b1",
      "url": "/v1/files/a91c4f7e2b6d48c05e3a1f9b7d2c6084?p=OWIyZjVjMWUtN2EzZC00ZjhiLTljMjEtNmQ0ZThhMGI1ZjE3&exp=1793410512&sig=3f9c1d7a2b6e4c8d0f1a5b9e3c7d2f6a8b0c4e2d"
    }
  ],
  "error": null,
  "warnings": [],
  "sandbox": false,
  "links": {
    "self": "/v1/jobs/4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08"
  }
}

Las URLs de files[] son firmadas y expiran a los 15 minutos. Vuelve a consultar el job para obtener URLs nuevas mientras el trabajo no expire (72 h).

wait=true o job asíncrono

wait=true hace que la petición espere hasta 25 segundos a que el job termine. Si termina a tiempo, responde 200 con el job en succeeded; si no, responde 202 con el job en curso y debes seguir consultándolo.

Aspectowait=trueJob asíncrono (wait=false)
Respuesta200 si termina en ≤ 25 s; 202 si no.Siempre 202.
Latencia de tu requestHasta 25 s (bloquea el hilo).Inmediata (~100–300 ms).
Ideal paraFormularios interactivos, validaciones puntuales.Procesos masivos, colas, cualquier operación real.
RiesgoTimeouts en gateways con límite de 30 s.Requiere polling o webhook.
RecomendaciónÚsalo sólo si el usuario está esperando en pantalla.Predeterminado para backend.
Shell
curl -sS -X POST "https://api.434alchemycode.com/v1/sat/domicilios" \
  -H "Authorization: Bearer $ALCHEMY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17", "wait": true}'
Si tu caso de uso es una mesa de control, usa el job asíncrono y guarda la evidencia al terminar.

Con wait=true el cuerpo cambia de 202 a 200, pero el objeto job conserva la misma forma. No vuelvas a enviar la consulta si recibes 202: ya está en curso.

Errores propios del servicio

CódigoHTTPCuándo ocurreReintentable
CREDENTIAL_MISSING422Falta la e.firma de la empresa.No
FIEL_EXPIRED422La e.firma está vencida.No
PORTAL_UNAVAILABLE503El portal del SAT no respondió.Sí
CREDENTIAL_INVALID422Credenciales rechazadas por el SAT.No

Además aplican los errores de plataforma (AUTH_REQUIRED, SCOPE_FORBIDDEN, QUOTA_EXCEEDED, RATE_LIMITED, COMPANY_NOT_FOUND, JOB_*). El catálogo completo está en Errores.

Ejemplos completos

Los ocho ejemplos incluyen autenticación, Content-Type, lectura de errores problem+json y el flujo completo del trabajo. Reemplaza $ALCHEMY_API_KEY por una llave real y ajusta el company_id.

CURL
# Estatus de Domicilio Fiscal · AlchemyCode API
curl -i -sS -X POST "https://api.434alchemycode.com/v1/sat/domicilios" \
  -H "Authorization: Bearer $ALCHEMY_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2c7a-3d0e-4a51-9b0e-5c2a7f9d1e44" \
  --data-binary @- <<'JSON'
{
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "wait": false
}
JSON

# 1) La respuesta es HTTP/1.1 202 Accepted con el job en estado "queued".
# 2) Consulta el job cada 2 s hasta que status sea "succeeded":
curl -sS "https://api.434alchemycode.com/v1/jobs/4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08" \
  -H "Authorization: Bearer $ALCHEMY_API_KEY"

# 3) El resultado viene en .result y los archivos en .files[].url
#    (URLs firmadas que expiran a los 15 minutos).
PHP
<?php
declare(strict_types=1);

/**
 * Estatus de Domicilio Fiscal · AlchemyCode API
 * Requiere PHP 8.1+ con ext-curl y ext-json. Sin dependencias externas.
 */

const ALCHEMY_BASE = 'https://api.434alchemycode.com/v1';

final class AlchemyError extends RuntimeException
{
    public function __construct(
        public readonly string $errorCode,
        public readonly int $status,
        public readonly bool $retryable,
        public readonly string $requestId,
        string $message,
    ) {
        parent::__construct($message, $status);
    }
}

function alchemy(string $method, string $path, ?array $payload = null): array
{
    $key = getenv('ALCHEMY_API_KEY');
    if ($key === false || $key === '') {
        throw new RuntimeException('Falta la variable de entorno ALCHEMY_API_KEY.');
    }

    $ch = curl_init(ALCHEMY_BASE . $path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . $key,
            'Accept: application/json',
            'Content-Type: application/json',
            'User-Agent: OmnicomERP/2.4 (+https://omnicom.mx)',
        ],
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT        => 45,
    ]);
    if ($payload !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
    }

    $raw    = curl_exec($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $errno  = curl_errno($ch);
    $error  = curl_error($ch);
    curl_close($ch);

    if ($errno !== 0 || $raw === false) {
        // Fallo de red (sin respuesta HTTP): reintenta con backoff exponencial.
        throw new RuntimeException("Error de red cURL {$errno}: {$error}", $errno);
    }

    $body = json_decode((string) $raw, true);
    if (!is_array($body)) {
        throw new RuntimeException("Respuesta no JSON (HTTP {$status}).");
    }
    if ($status >= 400) {
        // application/problem+json (RFC 9457): `code` es estable, `detail` explicativo.
        throw new AlchemyError(
            (string) ($body['code'] ?? 'UNKNOWN'),
            $status,
            (bool) ($body['retryable'] ?? false),
            (string) ($body['request_id'] ?? 'n/a'),
            sprintf('[%s] %s', $body['code'] ?? 'UNKNOWN', $body['detail'] ?? $body['title'] ?? 'Error'),
        );
    }
    return $body;
}

$payload = json_decode(<<<'JSON'
{
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "wait": false
}
JSON, true, 512, JSON_THROW_ON_ERROR);

try {
    $job = alchemy('POST', '/sat/domicilios', $payload);
    // 202 Accepted: el trabajo quedó en cola.
    printf("Trabajo %s en estado %s\n", $job['id'], $job['status']);

    $deadline = microtime(true) + 120;
    while (in_array($job['status'], ['queued', 'running'], true) && microtime(true) < $deadline) {
        sleep(2);
        $job = alchemy('GET', '/jobs/' . $job['id']);
    }

    if ($job['status'] !== 'succeeded') {
        fwrite(STDERR, "El trabajo terminó en estado {$job['status']}\n");
        exit(1);
    }

    echo json_encode($job['result'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), PHP_EOL;
    foreach ($job['files'] as $file) {
        printf("Archivo: %s (%s)\n", $file['url'], $file['content_type']);
    }
} catch (AlchemyError $e) {
    fwrite(STDERR, "{$e->getMessage()} (HTTP {$e->status}, request_id {$e->requestId})\n");
    if ($e->retryable) {
        // 429, 502, 503 o 504: reintenta con backoff (1 s, 2 s, 4 s…).
    }
    exit(1);
}
Node.js
// Estatus de Domicilio Fiscal · AlchemyCode API
// Requiere Node.js 18+ (fetch nativo). Ejecuta: node consulta.js

const BASE = 'https://api.434alchemycode.com/v1';
const API_KEY = process.env.ALCHEMY_API_KEY;
if (!API_KEY) throw new Error('Falta la variable de entorno ALCHEMY_API_KEY.');

async function alchemy(method, path, body) {
  const response = await fetch(BASE + path, {
    method,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const text = await response.text();
  const data = text ? JSON.parse(text) : null;
  if (!response.ok) {
    const problem = data ?? {};
    const error = new Error(`[${problem.code ?? 'UNKNOWN'}] ${problem.detail ?? response.statusText}`);
    Object.assign(error, {
      status: response.status,
      code: problem.code,
      retryable: problem.retryable ?? false,
      requestId: problem.request_id,
    });
    throw error;
  }
  return data;
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const payload = {
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "wait": false
};

async function main() {
  try {
    let job = await alchemy('POST', '/sat/domicilios', payload);
    console.log(`Trabajo ${job.id}: ${job.status}`); // 202 Accepted

    const deadline = Date.now() + 120_000;
    while (['queued', 'running'].includes(job.status) && Date.now() < deadline) {
      await sleep(2000);
      job = await alchemy('GET', `/jobs/${job.id}`);
    }
    if (job.status !== 'succeeded') {
      throw new Error(`El trabajo ${job.id} terminó en estado ${job.status}`);
    }

    console.log(JSON.stringify(job.result, null, 2));
    for (const file of job.files) {
      console.log(`${file.name}: ${file.url}`);
    }
  } catch (error) {
    console.error(error.message, {
      status: error.status,
      code: error.code,
      retryable: error.retryable,
      requestId: error.requestId,
    });
    process.exitCode = error.retryable ? 2 : 1;
  }
}

main();
Python
"""Estatus de Domicilio Fiscal · AlchemyCode API

Requiere Python 3.10+ y requests (pip install requests).
"""

from __future__ import annotations

import json
import os
import sys
import time

import requests

BASE = "https://api.434alchemycode.com/v1"
API_KEY = os.environ["ALCHEMY_API_KEY"]

session = requests.Session()
session.headers.update(
    {
        "Authorization": f"Bearer {API_KEY}",
        "Accept": "application/json",
        "Content-Type": "application/json",
        "User-Agent": "OmnicomERP/2.4 (+https://omnicom.mx)",
    }
)


class AlchemyError(RuntimeError):
    def __init__(self, status: int, problem: dict) -> None:
        self.status = status
        self.code = problem.get("code", "UNKNOWN")
        self.retryable = bool(problem.get("retryable"))
        self.request_id = problem.get("request_id")
        super().__init__(f"[{self.code}] {problem.get('detail') or problem.get('title')}")


def alchemy(method: str, path: str, payload: dict | None = None) -> dict:
    response = session.request(method, BASE + path, json=payload, timeout=(10, 45))
    if response.status_code >= 400:
        try:
            problem = response.json()
        except ValueError:
            problem = {"code": "UNKNOWN", "detail": response.text[:200]}
        raise AlchemyError(response.status_code, problem)
    return response.json()

payload = json.loads(r'''
{
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "wait": false
}
''')


def main() -> None:
    try:
        job = alchemy("POST", "/sat/domicilios", payload)
        print("Trabajo", job["id"], job["status"])  # 202 Accepted

        deadline = time.monotonic() + 120
        while job["status"] in ("queued", "running") and time.monotonic() < deadline:
            time.sleep(2)
            job = alchemy("GET", f"/jobs/{job['id']}")

        if job["status"] != "succeeded":
            raise SystemExit(f"El trabajo terminó en estado {job['status']}")

        print(json.dumps(job["result"], indent=2, ensure_ascii=False))
        for file in job["files"]:
            print(f"{file['name']}: {file['url']}")
    except AlchemyError as exc:
        print(f"{exc} (HTTP {exc.status}, request_id {exc.request_id})", file=sys.stderr)
        raise SystemExit(2 if exc.retryable else 1)


if __name__ == "__main__":
    main()
Go
// Estatus de Domicilio Fiscal · AlchemyCode API
// Requiere Go 1.21+. Ejecuta: go run consulta.go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

const baseURL = "https://api.434alchemycode.com/v1"

type problem struct {
	Code      string `json:"code"`
	Detail    string `json:"detail"`
	Title     string `json:"title"`
	RequestID string `json:"request_id"`
	Retryable bool   `json:"retryable"`
}

type jobFile struct {
	Name        string `json:"name"`
	ContentType string `json:"content_type"`
	URL         string `json:"url"`
}

func call(method, path string, payload any, out any) error {
	var body io.Reader
	if payload != nil {
		raw, err := json.Marshal(payload)
		if err != nil {
			return err
		}
		body = bytes.NewReader(raw)
	}

	req, err := http.NewRequest(method, baseURL+path, body)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("ALCHEMY_API_KEY"))
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "OmnicomERP/2.4 (+https://omnicom.mx)")

	client := &http.Client{Timeout: 45 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if resp.StatusCode >= 400 {
		var p problem
		_ = json.Unmarshal(raw, &p)
		return fmt.Errorf("[%s] %s (HTTP %d, request_id %s)", p.Code, p.Detail, resp.StatusCode, p.RequestID)
	}
	if out == nil {
		return nil
	}
	return json.Unmarshal(raw, out)
}

const payloadJSON = `{
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "wait": false
}`

type job struct {
	ID     string          `json:"id"`
	Status string          `json:"status"`
	Result json.RawMessage `json:"result"`
	Files  []jobFile       `json:"files"`
}

func main() {
	var payload map[string]any
	if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil {
		fmt.Fprintln(os.Stderr, "payload inválido:", err)
		os.Exit(1)
	}
	var current job
	if err := call("POST", "/sat/domicilios", payload, &current); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Printf("Trabajo %s: %s\n", current.ID, current.Status) // 202 Accepted

	deadline := time.Now().Add(2 * time.Minute)
	for (current.Status == "queued" || current.Status == "running") && time.Now().Before(deadline) {
		time.Sleep(2 * time.Second)
		if err := call("GET", "/jobs/"+current.ID, nil, &current); err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}
	if current.Status != "succeeded" {
		fmt.Fprintf(os.Stderr, "El trabajo terminó en estado %s\n", current.Status)
		os.Exit(1)
	}

	var result map[string]any
	_ = json.Unmarshal(current.Result, &result)
	pretty, _ := json.MarshalIndent(result, "", "  ")
	fmt.Println(string(pretty))
	for _, f := range current.Files {
		fmt.Printf("%s: %s\n", f.Name, f.URL)
	}
}
Java
// Estatus de Domicilio Fiscal · AlchemyCode API
// Requiere Java 17+ y Jackson (com.fasterxml.jackson.core:jackson-databind:2.17.2).
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public final class AlchemyConsulta {

    private static final String BASE = "https://api.434alchemycode.com/v1";
    private static final ObjectMapper JSON = new ObjectMapper();
    private static final HttpClient HTTP = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();

    private static JsonNode call(String method, String path, String body) throws Exception {
        String apiKey = System.getenv("ALCHEMY_API_KEY");
        if (apiKey == null || apiKey.isBlank()) {
            throw new IllegalStateException("Falta la variable de entorno ALCHEMY_API_KEY.");
        }

        HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(BASE + path))
                .timeout(Duration.ofSeconds(45))
                .header("Authorization", "Bearer " + apiKey)
                .header("Accept", "application/json")
                .header("Content-Type", "application/json")
                .header("User-Agent", "OmnicomERP/2.4 (+https://omnicom.mx)");
        builder.method(method, body == null
                ? HttpRequest.BodyPublishers.noBody()
                : HttpRequest.BodyPublishers.ofString(body));

        HttpResponse<String> response = HTTP.send(builder.build(), HttpResponse.BodyHandlers.ofString());
        JsonNode json = JSON.readTree(response.body());
        if (response.statusCode() >= 400) {
            throw new IllegalStateException(String.format(
                    "[%s] %s (HTTP %d, request_id %s)",
                    json.path("code").asText("UNKNOWN"),
                    json.path("detail").asText(json.path("title").asText("Error")),
                    response.statusCode(),
                    json.path("request_id").asText("n/a")));
        }
        return json;
    }

    public static void main(String[] args) throws Exception {
        String body = """
                {
                  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
                  "wait": false
                }
                """;

        try {
            JsonNode job = call("POST", "/sat/domicilios", body);
            System.out.println("Trabajo " + job.path("id").asText() + ": " + job.path("status").asText());

            long deadline = System.currentTimeMillis() + 120_000;
            while (("queued".equals(job.path("status").asText())
                    || "running".equals(job.path("status").asText()))
                    && System.currentTimeMillis() < deadline) {
                Thread.sleep(2_000);
                job = call("GET", "/jobs/" + job.path("id").asText(), null);
            }
            if (!"succeeded".equals(job.path("status").asText())) {
                System.err.println("El trabajo terminó en estado " + job.path("status").asText());
                System.exit(1);
            }

            System.out.println(JSON.writerWithDefaultPrettyPrinter()
                    .writeValueAsString(job.path("result")));
            for (JsonNode file : job.path("files")) {
                System.out.println(file.path("name").asText() + ": " + file.path("url").asText());
            }
        } catch (IllegalStateException error) {
            System.err.println(error.getMessage());
            System.exit(1);
        }
    }
}
DOTNET
// Estatus de Domicilio Fiscal · AlchemyCode API
// Requiere .NET 8+. Ejecuta: dotnet run
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;

const string BaseUrl = "https://api.434alchemycode.com/v1";
var apiKey = Environment.GetEnvironmentVariable("ALCHEMY_API_KEY")
    ?? throw new InvalidOperationException("Falta la variable ALCHEMY_API_KEY.");

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(45) };
http.DefaultRequestHeaders.Authorization = new("Bearer", apiKey);
http.DefaultRequestHeaders.Accept.ParseAdd("application/json");

async Task<JsonNode> CallAsync(string method, string path, string? body = null)
{
    using var request = new HttpRequestMessage(new HttpMethod(method), BaseUrl + path);
    if (body is not null)
    {
        request.Content = new StringContent(body, Encoding.UTF8, "application/json");
    }

    using var response = await http.SendAsync(request);
    var text = await response.Content.ReadAsStringAsync();
    var json = JsonNode.Parse(text) ?? new JsonObject();
    if (!response.IsSuccessStatusCode)
    {
        throw new HttpRequestException(
            $"[{json["code"]}] {json["detail"]} (HTTP {(int)response.StatusCode}, request_id {json["request_id"]})");
    }
    return json;
}

var payload = """
    {
      "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
      "wait": false
    }
    """;

try
{
    var job = await CallAsync("POST", "/sat/domicilios", payload);
    Console.WriteLine($"Trabajo {job["id"]}: {job["status"]}"); // 202 Accepted

    var deadline = DateTime.UtcNow.AddMinutes(2);
    while (((string?)job["status"] is "queued" or "running") && DateTime.UtcNow < deadline)
    {
        await Task.Delay(2000);
        job = await CallAsync("GET", $"/jobs/{job["id"]}");
    }
    if ((string?)job["status"] != "succeeded")
    {
        Console.Error.WriteLine($"El trabajo terminó en estado {job["status"]}");
        return 1;
    }

    Console.WriteLine(job["result"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
    foreach (var file in job["files"]!.AsArray())
    {
        Console.WriteLine($"{file!["name"]}: {file["url"]}");
    }
}
catch (HttpRequestException error)
{
    Console.Error.WriteLine(error.Message);
    return 1;
}

return 0;
Ruby
# Estatus de Domicilio Fiscal · AlchemyCode API
# Requiere Ruby 3.1+ (net/http y json son biblioteca estándar).
require 'json'
require 'net/http'
require 'uri'

BASE = 'https://api.434alchemycode.com/v1'
API_KEY = ENV.fetch('ALCHEMY_API_KEY')

class AlchemyError < StandardError
  attr_reader :code, :status, :retryable, :request_id

  def initialize(status, problem)
    @status = status
    @code = problem['code'] || 'UNKNOWN'
    @retryable = problem['retryable'] || false
    @request_id = problem['request_id']
    super("[#{@code}] #{problem['detail'] || problem['title']}")
  end
end

def alchemy(method, path, payload = nil)
  uri = URI("#{BASE}#{path}")
  request = method == 'GET' ? Net::HTTP::Get.new(uri) : Net::HTTP::Post.new(uri)
  request['Authorization'] = "Bearer #{API_KEY}"
  request['Accept'] = 'application/json'
  request['Content-Type'] = 'application/json'
  request['User-Agent'] = 'OmnicomERP/2.4 (+https://omnicom.mx)'
  request.body = JSON.generate(payload) if payload

  response = Net::HTTP.start(
    uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 10, read_timeout: 45
  ) { |http| http.request(request) }

  data = response.body.to_s.empty? ? {} : JSON.parse(response.body)
  raise AlchemyError.new(response.code.to_i, data) if response.code.to_i >= 400

  data
end

payload = JSON.parse(<<~JSON)
{
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "wait": false
}
JSON

begin
  job = alchemy('POST', '/sat/domicilios', payload)
  puts "Trabajo #{job['id']}: #{job['status']}" # 202 Accepted

  deadline = Time.now + 120
  while %w[queued running].include?(job['status']) && Time.now < deadline
    sleep 2
    job = alchemy('GET', "/jobs/#{job['id']}")
  end
  raise "El trabajo terminó en estado #{job['status']}" unless job['status'] == 'succeeded'

  puts JSON.pretty_generate(job['result'])
  job['files'].each { |file| puts "#{file['name']}: #{file['url']}" }
rescue AlchemyError => e
  warn "#{e.message} (HTTP #{e.status}, request_id #{e.request_id})"
  exit(e.retryable ? 2 : 1)
end
El company_id, el job_id y los archivos son de ejemplo. No los reutilices: cada cuenta sólo puede ver sus propias empresas y trabajos.

Continúa