Integración con APIs REST en Business Central

Guía completa para integrar Business Central con APIs REST externas

Introducción

Business Central permite integrarse fácilmente con APIs REST externas usando los objetos HttpClient y JsonToken. Esta guía te mostrará cómo implementar estas integraciones de manera efectiva.

Requisitos

  • Business Central versión 15.0 o superior
  • Permisos para crear objetos AL
  • Conocimientos básicos de JSON y HTTP

Configuración Básica

Antes de comenzar a consumir APIs, necesitamos configurar nuestro entorno y crear las estructuras básicas.

Crear un Codeunit para la Integración

codeunit 50100 "API Integration Management"
{
    trigger OnRun()
    begin
        
    end;

    local procedure InitializeHttpClient(var HttpClient: HttpClient)
    begin
        HttpClient.Clear();
        HttpClient.Timeout := 30000; // 30 segundos
        HttpClient.DefaultRequestHeaders.Add('User-Agent', 'Business Central Integration');
    end;
}

Consumir APIs REST

El proceso básico para consumir una API REST involucra varios pasos fundamentales.

GET Request Básico

local procedure GetApiData(Url: Text): Text
var
    HttpClient: HttpClient;
    HttpResponseMessage: HttpResponseMessage;
    ResponseText: Text;
begin
    InitializeHttpClient(HttpClient);
    
    if not HttpClient.Get(Url, HttpResponseMessage) then
        Error('Error al conectar con la API');
        
    if not HttpResponseMessage.IsSuccessStatusCode then
        Error('Error HTTP: %1', HttpResponseMessage.HttpStatusCode);
        
    HttpResponseMessage.Content.ReadAs(ResponseText);
    exit(ResponseText);
end;

POST Request con JSON

local procedure PostJsonData(Url: Text; JsonData: Text): Text
var
    HttpClient: HttpClient;
    HttpContent: HttpContent;
    HttpHeaders: HttpHeaders;
    HttpResponseMessage: HttpResponseMessage;
    ResponseText: Text;
begin
    InitializeHttpClient(HttpClient);
    
    HttpContent.WriteFrom(JsonData);
    HttpContent.GetHeaders(HttpHeaders);
    HttpHeaders.Clear();
    HttpHeaders.Add('Content-Type', 'application/json');
    
    if not HttpClient.Post(Url, HttpContent, HttpResponseMessage) then
        Error('Error al enviar datos a la API');
        
    if not HttpResponseMessage.IsSuccessStatusCode then
        Error('Error HTTP: %1', HttpResponseMessage.HttpStatusCode);
        
    HttpResponseMessage.Content.ReadAs(ResponseText);
    exit(ResponseText);
end;

Autenticación

La mayoría de APIs requieren algún tipo de autenticación. Aquí cubrimos los métodos más comunes.

API Key

local procedure AddApiKeyAuthentication(var HttpClient: HttpClient; ApiKey: Text)
begin
    HttpClient.DefaultRequestHeaders.Add('X-API-Key', ApiKey);
end;

Bearer Token

local procedure AddBearerTokenAuthentication(var HttpClient: HttpClient; Token: Text)
begin
    HttpClient.DefaultRequestHeaders.Add('Authorization', 'Bearer ' + Token);
end;

Manejo de Errores

Un manejo robusto de errores es crucial para las integraciones de producción.

local procedure SafeApiCall(Url: Text; var ErrorMessage: Text): Text
var
    HttpClient: HttpClient;
    HttpResponseMessage: HttpResponseMessage;
    ResponseText: Text;
begin
    Clear(ErrorMessage);
    
    try
        InitializeHttpClient(HttpClient);
        
        if not HttpClient.Get(Url, HttpResponseMessage) then begin
            ErrorMessage := 'No se pudo conectar con la API';
            exit('');
        end;
        
        case HttpResponseMessage.HttpStatusCode of
            200: begin
                HttpResponseMessage.Content.ReadAs(ResponseText);
                exit(ResponseText);
            end;
            401: ErrorMessage := 'No autorizado - verificar credenciales';
            404: ErrorMessage := 'Recurso no encontrado';
            429: ErrorMessage := 'Demasiadas peticiones - intentar más tarde';
            500: ErrorMessage := 'Error interno del servidor';
            else ErrorMessage := StrSubstNo('Error HTTP: %1', HttpResponseMessage.HttpStatusCode);
        end;
    except
        ErrorMessage := GetLastErrorText();
    end;
end;

Ejemplos Prácticos

Veamos algunos ejemplos reales de integración con APIs populares.

Ejemplo: Consultar Tasas de Cambio

local procedure GetExchangeRates(): Decimal
var
    HttpClient: HttpClient;
    HttpResponseMessage: HttpResponseMessage;
    JsonObject: JsonObject;
    JsonToken: JsonToken;
    ResponseText: Text;
    Rate: Decimal;
begin
    InitializeHttpClient(HttpClient);
    
    if HttpClient.Get('https://api.exchangerate-api.com/v4/latest/USD', HttpResponseMessage) then begin
        HttpResponseMessage.Content.ReadAs(ResponseText);
        
        if JsonObject.ReadFrom(ResponseText) then begin
            if JsonObject.SelectToken('rates.EUR', JsonToken) then begin
                Rate := JsonToken.AsValue().AsDecimal();
                exit(Rate);
            end;
        end;
    end;
    
    Error('No se pudo obtener la tasa de cambio');
end;

Buenas Prácticas

Consideraciones Importantes

  • Timeouts: Siempre configurar timeouts apropiados
  • Retry Logic: Implementar lógica de reintentos para errores temporales
  • Rate Limiting: Respetar los límites de la API
  • Logging: Registrar todas las llamadas para auditoria
  • Seguridad: Nunca hardcodear credenciales en el código
  • Testing: Probar exhaustivamente en ambiente de desarrollo

Configuración de Setup

table 50100 "API Integration Setup"
{
    fields
    {
        field(1; "Primary Key"; Code[10])
        {
            DataClassification = SystemMetadata;
        }
        field(10; "Base URL"; Text[250])
        {
            DataClassification = EndUserIdentifiableInformation;
        }
        field(20; "API Key"; Text[100])
        {
            DataClassification = EndUserPseudonymousIdentifiers;
        }
        field(30; "Timeout (ms)"; Integer)
        {
            DataClassification = SystemMetadata;
            InitValue = 30000;
        }
        field(40; "Retry Count"; Integer)
        {
            DataClassification = SystemMetadata;
            InitValue = 3;
        }
    }
    
    keys
    {
        key(PK; "Primary Key")
        {
            Clustered = true;
        }
    }
}