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.
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
reintentarcuando la confianza es baja. - Consumo de tokens de IA asociado a la operación.
Requisitos
company_id, el consumo se atribuye a esa empresa.Petición
/v1/captcha/resolucionesAPI key con scope services:run| Campo | Tipo | Descripción |
|---|---|---|
image_base64 | stringrequerido | Imagen PNG o JPEG en base64, sin el prefijo data:. |
portal | enumopcional | sat, imss, infonavit u otro. Por omisión otro. |
company_id | UUIDopcional | Opcional. Atribuye el consumo de IA a una empresa. |
Respuesta
La respuesta es 200 OK y trae el resultado en data. No se crea job, no hay polling y no aplica wait.
{
"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.
| Aspecto | Comportamiento |
|---|---|
| Respuesta | 200 OK con object: "result". |
| Timeout recomendado | 60 s de lectura; lotes grandes pueden acercarse a 30 s. |
| Idempotencia | No aplica Idempotency-Key: si reintentas, se ejecuta otra consulta. |
| Reintentos | Sólo ante 429, 502, 503 o 504, con backoff. |
| Límite de lote | Una imagen por petición; cuerpo máximo 2 MB. |
429 QUOTA_EXCEEDED sin consumir.Errores propios del servicio
| Código | HTTP | Cuándo ocurre | Reintentable |
|---|---|---|---|
VALIDATION_ERROR | 422 | Falta image_base64, no es base64 válido o excede el tamaño del cuerpo. | No |
SERVICE_UNAVAILABLE | 503 | El modelo de IA no está disponible; reintenta con backoff. | Sí |
QUOTA_EXCEEDED | 429 | Sin unidades disponibles en el periodo. | No |
PAYLOAD_TOO_LARGE | 413 | El 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.
# 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
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);
}
// 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();
"""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()
// 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
}
// 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);
}
}
}
// 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;
# 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
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.