AlchemyCode API docs
IA · captcha.resolve

Resolución de CAPTCHA

Resuelve CAPTCHAs de texto e imagen de los portales SAT, IMSS e Infonavit. Devuelve el texto, la confianza del modelo y el consumo de tokens de IA.

Síncrono · 200 1 unidad por consulta Sin costo en beta · consumo medido Requiere: Ninguno

Qué hace

Resuelve CAPTCHAs de texto e imagen de los portales SAT, IMSS e Infonavit con un modelo de visión. Devuelve el texto leído, la confianza y si conviene reintentar. Es síncrono y consume tokens de IA que se registran en el consumo de IA de tu cuenta.

Qué obtienes:

  • Texto del CAPTCHA y confianza del modelo.
  • Indicador reintentar cuando la confianza es baja.
  • Consumo de tokens de IA asociado a la operación.

Requisitos

Este servicio no requiere credenciales de empresa: puedes consultarlo con tu API key y, si envías company_id, el consumo se atribuye a esa empresa.
La latencia declarada en esta página es orientativa: 1 – 4 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/captcha/resolucionesAPI key con scope services:run
CampoTipoDescripción
image_base64stringrequeridoImagen PNG o JPEG en base64, sin el prefijo data:.
portalenumopcionalsat, imss, infonavit u otro. Por omisión otro.
company_idUUIDopcionalOpcional. Atribuye el consumo de IA a una empresa.
Cada consulta consume 1 unidad de tu cuota mensual, se cobre o no. El detalle del cálculo está en Límites y cuotas.

Respuesta

La respuesta es 200 OK y trae el resultado en data. No se crea job, no hay polling y no aplica wait.

JSON
{
  "object": "result",
  "service": "captcha.resolve",
  "data": {
    "texto": "A1B2C",
    "confianza": 0.943,
    "portal": "sat",
    "modelo": "gpt-4.1-mini",
    "reintentar": false
  },
  "warnings": [],
  "sandbox": false,
  "elapsed_ms": 3421
}

Los servicios síncronos también se miden: cada petición consume las unidades del catálogo y se registra en /v1/usage con su latencia.

Síncrono por diseño

El servicio responde en la misma petición; no existe job, wait ni webhook de resultado. Esto simplifica la integración, pero exige cuidar el timeout de tu cliente.

AspectoComportamiento
Respuesta200 OK con object: "result".
Timeout recomendado60 s de lectura; lotes grandes pueden acercarse a 30 s.
IdempotenciaNo aplica Idempotency-Key: si reintentas, se ejecuta otra consulta.
ReintentosSólo ante 429, 502, 503 o 504, con backoff.
Límite de loteUna imagen por petición; cuerpo máximo 2 MB.
Para volúmenes grandes divide en lotes y procesa en paralelo con un límite de concurrencia. La cuota se evalúa antes de ejecutar: si no hay unidades, la respuesta es 429 QUOTA_EXCEEDED sin consumir.

Errores propios del servicio

CódigoHTTPCuándo ocurreReintentable
VALIDATION_ERROR422Falta image_base64, no es base64 válido o excede el tamaño del cuerpo.No
SERVICE_UNAVAILABLE503El modelo de IA no está disponible; reintenta con backoff.Sí
QUOTA_EXCEEDED429Sin unidades disponibles en el periodo.No
PAYLOAD_TOO_LARGE413El cuerpo supera 2 MB; reduce o comprime la imagen.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
# Resolución de CAPTCHA · AlchemyCode API
# El CAPTCHA viaja como base64 SIN el prefijo data: (máximo 2 MB de cuerpo).
B64=$(base64 < captcha.png | tr -d '\n')

curl -i -sS -X POST "https://api.434alchemycode.com/v1/captcha/resoluciones" \
  -H "Authorization: Bearer $ALCHEMY_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{\"image_base64\":\"${B64}\",\"portal\":\"sat\"}"

# Respuesta síncrona 200: el resultado viene en .data, sin job ni polling.
# Si hay error, el cuerpo es application/problem+json con .code estable.
PHP
<?php
declare(strict_types=1);

/**
 * Resolución de CAPTCHA · 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 = [
    'image_base64' => base64_encode(file_get_contents(__DIR__ . '/captcha.png')),
    'portal' => 'sat',
];

try {
    $result = alchemy('POST', '/captcha/resoluciones', $payload);
    // 200 OK: el resultado viene en .data (servicio síncrono).
    echo json_encode($result['data'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), PHP_EOL;
} 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
// Resolución de CAPTCHA · 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 { readFileSync } = require('node:fs');

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

const payload = {
  image_base64: readFileSync('captcha.png').toString('base64'),
  portal: 'sat',
};

async function main() {
  try {
    const result = await alchemy('POST', '/captcha/resoluciones', payload);
    console.log(JSON.stringify(result.data, null, 2)); // 200 OK, síncrono
  } 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
"""Resolución de CAPTCHA · 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

import base64
from pathlib import Path

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 = {
    "image_base64": base64.b64encode(Path("captcha.png").read_bytes()).decode("ascii"),
    "portal": "sat",
}


def main() -> None:
    try:
        result = alchemy("POST", "/captcha/resoluciones", payload)
        print(json.dumps(result["data"], indent=2, ensure_ascii=False))  # 200 OK
    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
// Resolución de CAPTCHA · AlchemyCode API
// Requiere Go 1.21+. Ejecuta: go run consulta.go
package main

import (
	"bytes"
	"encoding/base64"
	"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)
}

func payload() map[string]any {
	raw, err := os.ReadFile("captcha.png")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	return map[string]any{
		"image_base64": base64.StdEncoding.EncodeToString(raw),
		"portal":       "sat",
	}
}

type syncResult struct {
	Data     map[string]any `json:"data"`
	Warnings []string       `json:"warnings"`
}

func main() {
	payload := payload()
	var result syncResult
	if err := call("POST", "/captcha/resoluciones", payload, &result); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	pretty, _ := json.MarshalIndent(result.Data, "", "  ")
	fmt.Println(string(pretty)) // 200 OK, servicio síncrono
}
Java
// Resolución de CAPTCHA · 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.node.ObjectNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
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 {
        var request = JSON.createObjectNode();
        request.put("image_base64", Base64.getEncoder().encodeToString(
                Files.readAllBytes(Path.of("captcha.png"))));
        request.put("portal", "sat");
        String body = JSON.writeValueAsString(request);

        try {
            JsonNode result = call("POST", "/captcha/resoluciones", body);
            System.out.println(JSON.writerWithDefaultPrettyPrinter()
                    .writeValueAsString(result.path("data")));
        } catch (IllegalStateException error) {
            System.err.println(error.getMessage());
            System.exit(1);
        }
    }
}
DOTNET
// Resolución de CAPTCHA · 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 = JsonSerializer.Serialize(new
{
    image_base64 = Convert.ToBase64String(File.ReadAllBytes("captcha.png")),
    portal = "sat",
});

try
{
    var result = await CallAsync("POST", "/captcha/resoluciones", payload);
    Console.WriteLine(result["data"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
}
catch (HttpRequestException error)
{
    Console.Error.WriteLine(error.Message);
    return 1;
}

return 0;
Ruby
# Resolución de CAPTCHA · AlchemyCode API
# Requiere Ruby 3.1+ (net/http y json son biblioteca estándar).
require 'base64'
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 = {
  'image_base64' => Base64.strict_encode64(File.binread('captcha.png')),
  'portal' => 'sat'
}

begin
  result = alchemy('POST', '/captcha/resoluciones', payload)
  puts JSON.pretty_generate(result['data']) # 200 OK, síncrono
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