Saltar al contenido principal

FEL API Usage Guide

This guide explains how to consume the RPAfelApi from other projects.

Table of Contents

Base URL

Production:

https://fel.rpapos.com/api/fel

Development:

https://fel-dev.rpapos.com/api/fel

Local Development:

http://localhost:5000/api/fel

Authentication

Most endpoints require an Authorization header with a token obtained from the certification provider (Digifact, Infile, etc.).

Get Token

Endpoint: POST /api/fel/GetToken

Request:

{
"UserName": "GT.000044653948.RPA_TEST",
"Password": "your-password"
}

Response:

{
"Success": true,
"response": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expira_en": "2024-01-01T12:00:00",
"otorgado_a": "GT.000044653948.RPA_TEST"
}
}

API Endpoints

1. Generate Certificate to Sign

Endpoint: POST /api/fel/generateCertificateToSign

Headers:

  • Authorization: Bearer token (required)
  • Content-Type: application/json

Request Body: DTE JSON object (see DTE Structure)

Response (Success - 200):

{
"rpaUUID": "550e8400-e29b-41d4-a716-446655440000",
"Codigo": 0,
"Mensaje": "OK",
"AcuseReciboSAT": "A-1234567890",
"NITCertificador": "12345678",
"NombreCertificador": "DIGIFACT S.A.",
"Autorizacion_Serie": "A",
"Autorizacion_Numero": "123456",
"Autorizacion_Text": "A-123456",
"FechaHoraCertificacion": "2024-01-01T12:00:00Z"
}

Response (Error - 400):

{
"error": "rpaUUID invalido."
}

2. Void Certificate

Endpoint: POST /api/fel/voidCertificate

Headers:

  • Authorization: Bearer token (required)
  • Content-Type: application/json

Request Body:

{
"NumeroDocumentoAAnular": "A-123456",
"NITEmisor": "44653948",
"IDReceptor": "49862952",
"FechaEmisionDocumentoAnular": "2024-01-01T12:00:00",
"FechaHoraAnulacion": "2024-01-01T13:00:00",
"MotivoAnulacion": "Error en facturación",
"Gface": "Digifact",
"rpaCertificador_Usuario": "GT.000044653948.RPA_TEST"
}

Response (Success - 200):

{
"ok": true,
"voidID": "firebase-document-id",
"data": {
"motivoAnulacion": "Error en facturación",
"fechaHoraAnulacion": "2024-01-01T13:00:00",
"numeroDocumentoAAnular": "A-123456",
"rpaUUID": "550e8400-e29b-41d4-a716-446655440000",
"serie": "A",
"autorizacion": "A-123456",
"numero": "123456"
}
}

3. Query Payer Info

Endpoint: POST /api/fel/QueryPayerInfo

Headers:

  • Authorization: Bearer token (optional, will auto-generate if not provided)
  • Content-Type: application/json

Request Body:

{
"IdentificationType": "NIT",
"gface": "Digifact",
"payerId": "49862952",
"EmisorUser": "RPA_TEST",
"EmisorNIT": "44653948"
}

Response:

{
"Success": true,
"TaxPayerId": "49862952",
"TaxPayerName": "RANGEL,CASTRO,,KENNETH,ZAIDD",
"PersonalId": null
}

4. Send Email

Endpoint: POST /api/fel/mailFel

Headers:

  • Content-Type: application/json

Request Body:

{
"rpaUUID": "550e8400-e29b-41d4-a716-446655440000",
"emails": ["customer@example.com", "accounting@example.com"]
}

Response:

{
"id": "mail-queue-id"
}

DTE Structure

The DTE (Documento Tributario Electrónico) is the main document structure for electronic invoices in Guatemala.

Required Fields

{
"ID": "DatosCertificados",
"DatosEmision": {
"ID": "DatosEmision",
"DatosGenerales": {
"Tipo": "FACT",
"FechaHoraEmision": "2024-01-01T12:00:00",
"CodigoMoneda": "GTQ",
"rpaUUID": "550e8400-e29b-41d4-a716-446655440000",
"rpaDE_Empresa": "Digifact",
"rpaCertificador_Usuario": "GT.000044653948.RPA_TEST",
"rpaCertificador_Clave": "password",
"rpaFisco_Usuario": ""
},
"Emisor": {
"NITEmisor": "44653948",
"NombreEmisor": "COMPANY NAME, S.A.",
"CodigoEstablecimiento": "1",
"NombreComercial": "COMPANY",
"AfiliacionIVA": "GEN",
"DireccionEmisor": {
"Direccion": "ADDRESS",
"CodigoPostal": "01001",
"Municipio": "Guatemala",
"Departamento": "Guatemala",
"Pais": "GT"
}
},
"Receptor": {
"NombreReceptor": "CUSTOMER NAME",
"IDReceptor": "49862952",
"DireccionReceptor": {
"Direccion": "ADDRESS",
"CodigoPostal": "01001",
"Municipio": "Guatemala",
"Departamento": "Guatemala",
"Pais": "GT"
}
},
"Frases": {
"Frase": [
{
"TipoFrase": "1",
"CodigoEscenario": "1"
}
]
},
"Items": {
"Item": [
{
"NumeroLinea": "1",
"BienOServicio": "B",
"Cantidad": "1",
"UnidadMedida": "UNI",
"Descripcion": "Product description",
"PrecioUnitario": "100.00",
"Precio": "100.00",
"Descuento": "0",
"Total": "100.00",
"Impuestos": {
"Impuesto": [
{
"NombreCorto": "IVA",
"CodigoUnidadGravable": "1",
"MontoGravable": "89.29",
"MontoImpuesto": "10.71"
}
]
}
}
]
},
"Totales": {
"GranTotal": "100.00",
"TotalImpuestos": {
"TotalImpuesto": [
{
"NombreCorto": "IVA",
"TotalMontoImpuesto": "10.71"
}
]
}
}
},
"extra": {
"subDomain": "DMO1",
"TipoEspecial": "CUI",
"PropinaAdenda": "1"
}
}

Document Types (Tipo)

  • FACT - Factura (Invoice)
  • NCRE - Nota de Crédito (Credit Note)
  • NDEB - Nota de Débito (Debit Note)
  • FESP - Factura Especial
  • FCAM - Factura Cambiaria
  • FCAP - Factura de Compra
  • FPEQ - Factura Pequeño Contribuyente
  • NABN - Nota de Abono
  • RECI - Recibo
  • RDON - Recibo de Donación

Certification Providers (rpaDE_Empresa)

  • Digifact - Digifact (default)
  • Infile - Infile
  • Fegora - Fegora
  • GuateFactura - GuateFactura

Code Examples

C# / .NET

using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class FelApiClient
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl;

public FelApiClient(string baseUrl = "https://fel.rpapos.com/api/fel")
{
_httpClient = new HttpClient();
_baseUrl = baseUrl;
}

public async Task<string> GetTokenAsync(string userName, string password)
{
var request = new
{
UserName = userName,
Password = password
};

var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync($"{_baseUrl}/GetToken", content);
response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<TokenResponse>(responseJson);

return result.response.token;
}

public async Task<CertificateResponse> GenerateCertificateAsync(
object dteJson,
string token)
{
var json = JsonSerializer.Serialize(dteJson);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var request = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/generateCertificateToSign")
{
Content = content
};
request.Headers.Add("Authorization", token);

var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<CertificateResponse>(responseJson);
}
}

JavaScript / TypeScript

class FelApiClient {
private baseUrl: string;

constructor(baseUrl: string = 'https://fel.rpapos.com/api/fel') {
this.baseUrl = baseUrl;
}

async getToken(userName: string, password: string): Promise<string> {
const response = await fetch(`${this.baseUrl}/GetToken`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ UserName: userName, Password: password }),
});

const data = await response.json();
if (!data.Success) {
throw new Error('Failed to get token');
}

return data.response.token;
}

async generateCertificate(dteJson: any, token: string): Promise<any> {
const response = await fetch(`${this.baseUrl}/generateCertificateToSign`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token,
},
body: JSON.stringify(dteJson),
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to generate certificate');
}

return await response.json();
}

async voidCertificate(request: any, token: string): Promise<any> {
const response = await fetch(`${this.baseUrl}/voidCertificate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token,
},
body: JSON.stringify(request),
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to void certificate');
}

return await response.json();
}

async queryPayerInfo(request: any, token?: string): Promise<any> {
const headers: any = {
'Content-Type': 'application/json',
};

if (token) {
headers['Authorization'] = token;
}

const response = await fetch(`${this.baseUrl}/QueryPayerInfo`, {
method: 'POST',
headers,
body: JSON.stringify(request),
});

return await response.json();
}

async sendEmail(rpaUUID: string, emails: string[]): Promise<any> {
const response = await fetch(`${this.baseUrl}/mailFel`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ rpaUUID, emails }),
});

return await response.json();
}
}

// Usage
const client = new FelApiClient();
const token = await client.getToken('GT.000044653948.RPA_TEST', 'password');
const certificate = await client.generateCertificate(dteJson, token);

Python

import requests
import json

class FelApiClient:
def __init__(self, base_url='https://fel.rpapos.com/api/fel'):
self.base_url = base_url
self.session = requests.Session()

def get_token(self, user_name, password):
url = f'{self.base_url}/GetToken'
payload = {
'UserName': user_name,
'Password': password
}
response = self.session.post(url, json=payload)
response.raise_for_status()
data = response.json()
if not data.get('Success'):
raise Exception('Failed to get token')
return data['response']['token']

def generate_certificate(self, dte_json, token):
url = f'{self.base_url}/generateCertificateToSign'
headers = {
'Authorization': token,
'Content-Type': 'application/json'
}
response = self.session.post(url, json=dte_json, headers=headers)
response.raise_for_status()
return response.json()

def void_certificate(self, request_data, token):
url = f'{self.base_url}/voidCertificate'
headers = {
'Authorization': token,
'Content-Type': 'application/json'
}
response = self.session.post(url, json=request_data, headers=headers)
response.raise_for_status()
return response.json()

def query_payer_info(self, request_data, token=None):
url = f'{self.base_url}/QueryPayerInfo'
headers = {'Content-Type': 'application/json'}
if token:
headers['Authorization'] = token
response = self.session.post(url, json=request_data, headers=headers)
return response.json()

def send_email(self, rpa_uuid, emails):
url = f'{self.base_url}/mailFel'
payload = {
'rpaUUID': rpa_uuid,
'emails': emails
}
response = self.session.post(url, json=payload)
response.raise_for_status()
return response.json()

# Usage
client = FelApiClient()
token = client.get_token('GT.000044653948.RPA_TEST', 'password')
certificate = client.generate_certificate(dte_json, token)

Swift (iOS)

import Foundation

class FelApiClient {
let baseURL: String

init(baseURL: String = "https://fel.rpapos.com/api/fel") {
self.baseURL = baseURL
}

func getToken(userName: String, password: String, completion: @escaping (String?, Error?) -> Void) {
let url = URL(string: "\(baseURL)/GetToken")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

let body: [String: Any] = [
"UserName": userName,
"Password": password
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)

URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
completion(nil, error)
return
}

if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let success = json["Success"] as? Bool,
success,
let response = json["response"] as? [String: Any],
let token = response["token"] as? String {
completion(token, nil)
} else {
completion(nil, NSError(domain: "FelApi", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get token"]))
}
}.resume()
}

func generateCertificate(dteJson: [String: Any], token: String, completion: @escaping ([String: Any]?, Error?) -> Void) {
let url = URL(string: "\(baseURL)/generateCertificateToSign")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(token, forHTTPHeaderField: "Authorization")
request.httpBody = try? JSONSerialization.data(withJSONObject: dteJson)

URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
completion(nil, error)
return
}

if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
completion(json, nil)
} else {
completion(nil, NSError(domain: "FelApi", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"]))
}
}.resume()
}
}

Java (Android)

import okhttp3.*;
import org.json.JSONObject;
import java.io.IOException;

public class FelApiClient {
private final String baseUrl;
private final OkHttpClient client;

public FelApiClient(String baseUrl) {
this.baseUrl = baseUrl;
this.client = new OkHttpClient();
}

public void getToken(String userName, String password, Callback callback) {
JSONObject body = new JSONObject();
try {
body.put("UserName", userName);
body.put("Password", password);
} catch (Exception e) {
callback.onFailure(null, new IOException(e));
return;
}

RequestBody requestBody = RequestBody.create(
body.toString(),
MediaType.parse("application/json")
);

Request request = new Request.Builder()
.url(baseUrl + "/GetToken")
.post(requestBody)
.addHeader("Content-Type", "application/json")
.build();

client.newCall(request).enqueue(callback);
}

public void generateCertificate(JSONObject dteJson, String token, Callback callback) {
RequestBody requestBody = RequestBody.create(
dteJson.toString(),
MediaType.parse("application/json")
);

Request request = new Request.Builder()
.url(baseUrl + "/generateCertificateToSign")
.post(requestBody)
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", token)
.build();

client.newCall(request).enqueue(callback);
}
}

Error Handling

Common HTTP Status Codes

  • 200 OK - Request successful
  • 400 Bad Request - Invalid request data (missing rpaUUID, invalid DTE structure, etc.)
  • 401 Unauthorized - Invalid or missing authorization token
  • 406 Not Acceptable - Document already signed (will attempt to retrieve from provider)
  • 500 Internal Server Error - Server error

Error Response Format

{
"error": "Error message description",
"code": 0,
"message": "Error message",
"description": "Detailed error description"
}

Deployment

Running Locally

  1. Navigate to the project directory:
cd csharp/FelApi/RPAfelApi
  1. Restore dependencies:
dotnet restore
  1. Run the application:
dotnet run

The API will be available at http://localhost:5000

Docker

Build the image:

docker build -t fel-api csharp/FelApi/RPAfelApi

Run the container:

docker run -d -p 8080:8080 -e ASPNETCORE_ENVIRONMENT=Production fel-api

Google Cloud Run

Deploy to Cloud Run:

gcloud builds submit --config cloudbuild.yaml --projectId barto-dev

Best Practices

  1. Always validate DTE structure before sending to the API
  2. Store tokens securely and refresh them before expiration
  3. Handle async responses - Some providers (Infile, Fegora) may return 406 and require polling
  4. Use retry logic for transient failures
  5. Log all requests for debugging and audit purposes
  6. Handle special cases:
    • Special NITs that require specific phrases
    • Tip handling via adenda
    • CUI/EXT identification types
  7. Monitor Pub/Sub topics for success/failure events if you need async notifications

Support

For issues or questions, check:

  • API documentation in /docs folder
  • Test examples in /felApiTest folder
  • XML examples in /docs/FEL_Digifact/Ejemplos_XML_FER/