Integration

API

Technical reference for the Enterprise API: authentication, test environment, permissions, errors and every API in detail.

For: technical teams at client companies. Terms (association, collaborator, association RFC, association ticket, multi-RFC type…) are defined in Concepts and the values of each catalog are in Catalogs. In the API, collaborators are called users (userId).


General usage

Authentication

Every request carries the B2B user’s API token:

Authorization: Bearer <token>
EnvironmentBase URLToken
Productionhttps://api.fotofacturas.ai/B2B/<Controller>/<API>Business web app, under API y plan.
Testhttps://api-staging.fotofacturas.ai/B2B/<Controller>/<API>Test business web app, under API y plan.

The test environment works the same as production, but tickets aren’t really invoiced: a simulator moves them through statuses based on the last 4 digits of the card (see Ticket simulation).

The API only lets you view and modify the information in your account’s association tree: any association, user, RFC or ticket outside that tree is rejected.

API permissions

Each API requires a permission enabled on your account. Fotofacturas enables them according to your contract; to enable one, request it by name. Without the permission, the API responds No estas autorizado para usar esta API. Ping doesn’t require a permission.

Response

Every API returns this object (JSON in camelCase):

FieldTypeDescription
isSuccessbooleanWhether the operation succeeded.
messagestring | nullResult message; on errors, the reason.
dataobject | nullRequested information (only in the APIs that return data).

Always check isSuccess: business errors are returned with HTTP 200.

{
  "isSuccess": false,
  "message": "El RFC XAXX010101000 no está registrado",
  "data": null
}

Errors

HTTPWhenmessage
200Success or a validation or business error (invalid data, resource outside your tree, plan limit, API without permission…).The error reason.
400Malformed JSON, wrong data type or a required field is missing.Datos inválidos en: userId, rfc or Datos inválidos en la petición.
401A problem with the token (see the next table).The reason.
429More than 500 requests per minute from the same IP.Demasiadas peticiones. Por favor, intenta más tarde.
500Unexpected error.Error inesperado en el sistema

Authentication errors (HTTP 401):

MessageCause
Falta header de autorizaciónAuthorization wasn’t sent.
Formato inválido del header de autorizaciónThe header doesn’t start with Bearer .
Falta el token en el header de autorizaciónThere’s no token after Bearer.
Token no válidoThe token doesn’t exist or the account is inactive.
Usuario no autorizado para usar este servicioThe token isn’t from a B2B user.
Servicio no disponible, reintente en un minutoThe service is starting up.

Common errors (HTTP 200, isSuccess: false):

MessageCause
No estas autorizado para usar esta APIYour account doesn’t have that API’s permission.
Por favor reintente en unos momentosThe service is starting up.
La asociación no le pertenece / La asociación no te perteneceThe association isn’t in your tree.
El usuario no pertenece a tus asociaciones / Este usuario no pertenece a su asociaciónThe user isn’t in your tree.
El usuario no existe / La asociación no existe / El ticket no existeThe id doesn’t exist.
El usuario no está activoA ticket was uploaded for an inactive user.
El RFC es obligatorio / El RFC {rfc} no es válidoThe RFC is missing or its format isn’t valid.
El RFC {rfc} no está registradoThe RFC isn’t among the ones the user or association can use.
El RFC {rfc} está deshabilitadoThe RFC is disabled.
El plan solo permite {n} RFCs en la asociación / El plan solo permite {n} RFCs por usuarioThe plan’s RFC limit was reached.
Has alcanzado tu límite mensual de {n} tickets... / Has alcanzado tu límite personal mensual de {n} tickets...The group’s or user’s monthly ticket limit was reached.
La fecha de inicio es obligatoria / La fecha de fin es obligatoria / La fecha de inicio no puede ser mayor a la fecha de finMissing or invalid date range.
El mes debe estar entre 1 y 12 / Año inválido / No se puede consultar un mes futuroInvalid month or year.

Formats

  • Input:
    • GET: query params.
    • POST/PUT without files: JSON.
    • APIs with files (CSF, logos, ticket image): multipart/form-data. For several files in one field, repeat the key (for example CSFs); nested fields use a dot (for example Config.CompanyName).
  • Input dates (startDate, endDate): yyyy-MM-dd, as a Mexico date, covering the whole day.
  • Response dates: Mexico time, without a time zone.
  • Monthly reports (month, year): the full calendar month in Mexico time.

Association

GetAssociationList

GET /B2B/AssociationV1/GetAssociationList · No parameters

Flat list of every association in your tree, sorted by id, with their RFCs. parentId is null for the root association.

{
  "data": [
    { "associationId": 41, "name": "FOTOFACTURAS", "parentId": null, "rfcs": [{ "rfc": "FFA200101AB1", "isActive": true }] },
    { "associationId": 42, "name": "Sucursal Norte", "parentId": 41, "rfcs": [] }
  ],
  "isSuccess": true,
  "message": null
}

GetAssociationTree

GET /B2B/AssociationV1/GetAssociationTree · No parameters

Your account’s association tree, with their RFCs.

{
  "data": {
    "associationId": 41,
    "name": "FOTOFACTURAS",
    "rfcs": [{ "rfc": "FFA200101AB1", "isActive": true }],
    "children": [
      { "associationId": 42, "name": "Sucursal Norte", "rfcs": [], "children": [] }
    ]
  },
  "isSuccess": true,
  "message": null
}

GetAssociationById

GET /B2B/AssociationV1/GetAssociationById · int associationId

Details of an association: general data, users, RFCs and configuration.

{
  "data": {
    "associationId": 42,
    "name": "Sucursal Norte",
    "parentId": 41,
    "createDate": "2026-10-01T10:15:00",
    "updateDate": "2026-10-05T12:00:00",
    "users": [ ... ],
    "rfcs": [
      {
        "userTaxInfoId": 10423,
        "rfc": "FFA200101AB1",
        "name": "FOTOFACTURAS SA DE CV",
        "csfFile": "https://...",
        "isVerified": true,
        "isActive": true,
        "createDate": "2026-10-01T10:20:00",
        "updateDate": "2026-10-01T10:20:00"
      }
    ],
    "config": {
      "logoSquare": "https://...",
      "logoRectangle": null,
      "companyName": "Sucursal Norte",
      "defaultRegimenFiscalId": 601,
      "defaultUsoCfdiId": "G03",
      "defaultTicketPaymentMethodId": "01",
      "canChangeRegimenFiscal": true,
      "canChangeUsoCfdi": true,
      "canChangePaymentMethod": true,
      "styleRoundness": 8,
      "styleSidebarColor": "#1E293B",
      "styleAccentColor": "#3B82F6",
      "styleAccentStrongColor": "#1D4ED8"
    }
  },
  "isSuccess": true,
  "message": null
}
  • users: same format as GetUsersByAssociation.
  • rfcs: the association’s RFCs, active and disabled. csfFile is a temporary URL.
  • config: always present; fields that aren’t configured return their default value. Logos are public URLs or null.

CreateAssociation

POST (multipart/form-data) /B2B/AssociationV1/CreateAssociation

Creates a child association. Optionally receives its configuration and one or more CSFs to register its RFCs. If any CSF is invalid, nothing is created.

FieldTypeRequiredDescription
NamestringYesAssociation name.
ParentIdintYesParent association (in your tree).
CSFsPDF file(s)NoThe association’s CSFs. Repeat the key for several. No repeated RFCs and without exceeding multiRfcPerAssociation.
Config.*NoUpdateAssociationConfig fields with the Config. prefix (for example Config.CompanyName).
{ "data": 43, "isSuccess": true, "message": "Guardada correctamente" }

data is the created associationId.

UpdateAssociation

PUT (JSON) /B2B/AssociationV1/UpdateAssociation

Changes the name and, optionally, the parent of an association.

{ "associationId": 42, "name": "Sucursal Norte", "parentId": 41 }
  • parentId is optional; if it isn’t sent or is the same, the association doesn’t move.
  • The root association can’t change its parent.
  • The new parent must be in your tree and can’t be the association itself or one of its descendants.

Response: message Actualizada correctamente.

UpdateAssociationConfig

PUT (multipart/form-data) /B2B/AssociationV1/UpdateAssociationConfig

Updates an association’s configuration. Only the fields you send change.

FieldTypeDescription
AssociationIdintRequired.
LogoSquare / LogoRectangleJPG or PNG fileSquare / rectangular logo (max. 4 MB). Replaces the previous one.
CompanyNamestringCompany name to display (max. 150 characters).
DefaultRegimenFiscalIdintDefault tax regime.
DefaultUsoCfdiIdstringDefault CFDI use. Requires a tax regime (the one sent or the saved one) and must be valid for it.
DefaultTicketPaymentMethodIdstringDefault payment method.
CanChangeRegimenFiscal / CanChangeUsoCfdi / CanChangePaymentMethodboolWhether the user can change that value when uploading a ticket.
StyleRoundnessintInterface roundness (non-negative).
StyleSidebarColor / StyleAccentColor / StyleAccentStrongColorstringColor #RRGGBB or #RRGGBBAA.

Response: message Configuración actualizada.

SaveAssociationCSF

POST (multipart/form-data) /B2B/AssociationV1/SaveAssociationCSF

Registers or updates an association RFC by uploading its CSF. If the RFC already exists in the association, it’s updated and reactivated; otherwise it’s created. Creating or reactivating respects multiRfcPerAssociation.

FieldTypeRequired
AssociationIdintYes
CSFPDF fileYes

Response: message CSF creada or CSF actualizada.

ChangeAssociationCSFStatus

PUT (JSON) /B2B/AssociationV1/ChangeAssociationCSFStatus

Activates or disables an association RFC. Activating respects multiRfcPerAssociation.

{ "associationId": 42, "rfc": "FFA200101AB1", "isActive": false }

Response: message CSF deshabilitada (or activated).


Miscellaneous

Ping

GET /B2B/MiscellaneousV1/Ping · No parameters · No permission required

Confirms that the API responds and the token is valid. Response: message Pong.

GetAccountInfo

GET /B2B/MiscellaneousV1/GetAccountInfo · No parameters

The B2B user’s data, their plan and the current month’s usage.

{
  "data": {
    "username": "foto.facturas",
    "name": "Laura Méndez",
    "email": "fotofacturas@fotofacturas.ai",
    "phone": "5512345678",
    "isActive": true,
    "createDate": "2026-01-10T09:00:00",
    "updateDate": "2026-01-10T09:00:00",
    "associationAndPlan": {
      "associationId": 41,
      "name": "FOTOFACTURAS",
      "createDate": "2026-01-10T09:00:00",
      "updateDate": "2026-01-10T09:00:00",
      "ticketsMonthlyLimit": 1000,
      "ticketsMonthlyLimitPerUser": 50,
      "canPictureInvoice": true,
      "multiRfcPerUser": 5,
      "multiRfcPerAssociation": 3,
      "multiRfcType": "M",
      "ticketsUsed": 21,
      "associationsCount": 4,
      "associationActiveTaxInfosCount": 3,
      "associationInactiveTaxInfosCount": 1,
      "userActiveTaxInfosCount": 12,
      "userInactiveTaxInfosCount": 2
    }
  },
  "isSuccess": true,
  "message": null
}
FieldDescription
ticketsMonthlyLimit, ticketsMonthlyLimitPerUser, canPictureInvoice, multiRfcPerUser, multiRfcPerAssociation, multiRfcTypePlan limits (see Concepts › Plan and limits).
ticketsUsedTickets in the current month across the whole tree, excluding discarded ones.
associationsCountAssociations in the tree, including the root.
associationActiveTaxInfosCount / associationInactiveTaxInfosCountActive / disabled association RFCs.
userActiveTaxInfosCount / userInactiveTaxInfosCountActive / disabled user RFCs.

associationAndPlan is null if the account has no association assigned.

GetTicketPaymentMethods

GET /B2B/MiscellaneousV1/GetTicketPaymentMethods · No parameters

Catalog of enabled payment methods.

{
  "data": [
    { "ticketPaymentMethodId": "01", "name": "Efectivo" },
    { "ticketPaymentMethodId": "28", "name": "Tarjeta de débito" }
  ],
  "isSuccess": true,
  "message": null
}

GetRegimenFiscalAndCFDI

GET /B2B/MiscellaneousV1/GetRegimenFiscalAndCFDI · No parameters

Catalog of tax regimes with the valid CFDI uses for each one.

{
  "data": [
    {
      "regimenFiscalId": 626,
      "name": "Régimen Simplificado de Confianza",
      "isFisica": true,
      "isMoral": true,
      "cfdi": [{ "usoCFDIId": "G03", "name": "Gastos en general" }]
    }
  ],
  "isSuccess": true,
  "message": null
}

GetTicketCodes

GET /B2B/MiscellaneousV1/GetTicketCodes · No parameters

Catalog of ticket codes (result or rejection reason).

{
  "data": [
    { "ticketCodeId": 1, "category": "Ticket", "description": "El ticket fue creado.", "userMessage": null, "isSuccessCode": null }
  ],
  "isSuccess": true,
  "message": null
}
  • description: description of the code.
  • userMessage: message for the end user; can be null.
  • isSuccessCode: whether the code is a success result; can be null.

SAT products and services

Hierarchical catalog Division → Family → Class → Product. Keys are prefixes (division 50 → family 5010 → class 501015 → products 501015xx). URL: /B2B/MiscellaneousV1/<API>.

APIMethodParameters (optional)Response (data)
GetSatProductDivisionsGETNone[{ "satProductDivisionId", "description" }]
GetSatProductFamiliesGETsatProductDivisionId[{ "satProductFamilyId", "description", "satProductDivisionId" }]
GetSatProductClassesGETsatProductFamilyId[{ "satProductClassId", "description", "satProductFamilyId" }]
GetSatProductsGETsatProductClassId, satProductFamilyId, satProductDivisionId, satProductId[{ "satProductId", "description", "similares", "satProductClassId" }]

For GetSatProducts, send at least one filter: without filters it returns the full catalog.


Ticket

GetTicketById

GET /B2B/TicketV1/GetTicketById · int userTicketId

Details of a ticket, including the invoice data if it has been invoiced.

{
  "data": {
    "userTicketId": 88254,
    "userId": 3,
    "ticketUrl": "https://...",
    "pdfUrl": "https://...",
    "xmlUrl": "https://...",
    "ticketDate": "2026-10-04T00:00:00",
    "ticketAmount": 350.50,
    "ticketPaymentMethodId": "28",
    "ticketPaymentMethod": "Tarjeta de débito",
    "ticketStatusId": 11,
    "ticketStatus": "SIGNED",
    "taxIdentifier": "XXXX007766JH7",
    "taxName": "MARIO DIAZ",
    "clientName": "Mario Díaz",
    "clientEmail": "fotofacturas@fotofacturas.ai",
    "ticketCodeId": 2,
    "invoiceAttemptsCount": 1,
    "invoiceMethod": 2,
    "ticketOriginId": 4,
    "ticketOrigin": "API Enterprise",
    "associationId": 42,
    "xmlData": { ... },
    "createDate": "2026-10-04T20:10:02",
    "updateDate": "2026-10-05T09:30:00"
  },
  "isSuccess": true,
  "message": null
}
  • ticketUrl, pdfUrl and xmlUrl are temporary URLs. pdfUrl and xmlUrl are null if there’s no invoice.
  • xmlData: data read from the invoice XML (null if there’s no XML).
  • taxIdentifier / taxName: the RFC and name being invoiced.
  • invoiceMethod: how the invoice was obtained (null if not invoiced).
  • ticketOriginId / ticketOrigin: the channel the ticket was uploaded through.
  • associationId: the ticket’s association (the user’s or, for an association ticket, the association itself).

Status, code, invoicing method and origin values are in Catalogs.

CreateUserTicket

POST (multipart/form-data) /B2B/TicketV1/CreateUserTicket

Uploads a ticket on behalf of a user. The RFC must be one the user can use according to the plan’s multi-RFC type.

FieldTypeRequiredDescription
UserIdintYesAn active user in your tree.
ImagefileYesPhoto of the ticket: JPG, PNG, WEBP, HEIC or PDF (max. 10 MB).
RfcstringYesRFC to invoice to. It must be registered and active.
TicketPaymentMethodIdstringYesGetTicketPaymentMethods catalog.
RegimenFiscalIdintYesGetRegimenFiscalAndCFDI catalog.
UsoCfdiIdstringYesMust be valid for the tax regime.
CardLastFourstringConditionalLast 4 digits of the card. Required with 04 and 28. In test it selects the simulator scenario.
{ "data": 88255, "isSuccess": true, "message": "Ticket creado" }

data is the created userTicketId. It’s rejected if the user or the group reached their monthly limit or if the plan doesn’t allow uploading tickets.

CreateAssociationTicket

POST (multipart/form-data) /B2B/TicketV1/CreateAssociationTicket

Uploads a ticket on behalf of an association. The RFC must be one of that association’s RFCs.

FieldTypeRequired
AssociationIdintYes
Image, Rfc, TicketPaymentMethodId, RegimenFiscalId, UsoCfdiId, CardLastFourSame as in CreateUserTicket.
{ "data": 88256, "isSuccess": true, "message": "Ticket creado" }

Report

Reports cover both user and association tickets and exclude discarded tickets.

Optional associationId filter in GetTicketCountByDay, GetPeriodStats, GetTicketFamilyStats, GetTicketStatuses and GetTicketsByMonth:

  • Without it, the report covers your account’s whole tree.
  • With it, it covers that association and its descendants. If it isn’t in your tree, the response is La asociación no le pertenece.
  • In GetPeriodStats, ticketsMonthlyLimit is always the account plan’s limit.

GetTicketCountByDay

GET /B2B/ReportV1/GetTicketCountByDay · startDate, endDate (required) · associationId (optional)

Tickets per day in a range, including days with no tickets. average = total tickets / days in the range.

{
  "data": {
    "days": [
      { "date": "2026-10-01", "ticketCount": 0 },
      { "date": "2026-10-02", "ticketCount": 3 },
      { "date": "2026-10-03", "ticketCount": 1 }
    ],
    "average": 1.3333333333333333
  },
  "isSuccess": true,
  "message": null
}

GetPeriodStats

GET /B2B/ReportV1/GetPeriodStats · month (1-12), year (required; future months aren’t allowed) · associationId (optional)

Statistics for a full month compared with the previous month.

{
  "data": {
    "currentPeriod": {
      "startDate": "2026-10-01T00:00:00",
      "endDate": "2026-10-31T23:59:59.999",
      "processedTicketCount": 8,
      "waitingTicketCount": 1,
      "cancelledTicketCount": 12,
      "totalInvoiced": 2983.76,
      "totalIVATaxInvoiced": 477.40,
      "totalTicketsUsed": 21,
      "ticketsMonthlyLimit": 1000
    },
    "previousPeriod": { ... }
  },
  "isSuccess": true,
  "message": null
}
FieldDescription
processedTicketCountInvoiced tickets (11).
cancelledTicketCountNot-invoiceable tickets (10).
waitingTicketCountTickets in progress (any other status).
totalTicketsUsedSum of the three above.
totalInvoicedSum of the amounts of invoiced tickets.
totalIVATaxInvoicedtotalInvoiced × 16%.
ticketsMonthlyLimitThe plan’s monthly ticket limit.

GetTicketFamilyStats

GET /B2B/ReportV1/GetTicketFamilyStats · startDate, endDate (required) · associationId (optional)

Invoiced products grouped by SAT catalog family.

{
  "data": [
    { "familyId": "1510", "description": "Combustibles", "count": 42 },
    { "familyId": "5019", "description": "Alimentos preparados", "count": 17 }
  ],
  "isSuccess": true,
  "message": null
}

familyId is the family key (satProductFamilyId in GetSatProductFamilies).

GetTicketStatuses

GET /B2B/ReportV1/GetTicketStatuses · startDate, endDate (required) · associationId (optional)

Tickets in a range with their status and code.

{
  "data": [
    {
      "userTicketId": 88254,
      "createDate": "2026-10-04T20:10:02",
      "ticketStatus": { "ticketStatusId": 5, "name": "WAITING INVOICE EMAIL" },
      "ticketCode": { "ticketCodeId": 1, "name": "El ticket fue creado." }
    }
  ],
  "isSuccess": true,
  "message": null
}

ticketCode.name is the description from the GetTicketCodes catalog.

GetTicketsByMonth

GET /B2B/ReportV1/GetTicketsByMonth · month (1-12), year (required; future months aren’t allowed) · associationId (optional)

Tickets for a full month, newest first. Same format as GetTicketById, without xmlData, invoiceMethod, ticketOriginId, ticketOrigin or associationId.

{
  "data": [
    {
      "userTicketId": 88254,
      "userId": 3,
      "ticketUrl": "https://...",
      "pdfUrl": null,
      "xmlUrl": null,
      "ticketDate": null,
      "ticketAmount": null,
      "ticketPaymentMethodId": "28",
      "ticketPaymentMethod": "Tarjeta de débito",
      "ticketStatusId": 5,
      "ticketStatus": "WAITING INVOICE EMAIL",
      "taxIdentifier": "XXX980317KJ7",
      "taxName": "MARIO DIAZ",
      "clientName": "MARIO DIAZ",
      "clientEmail": "fotofacturas@fotofacturas.ai",
      "ticketCodeId": 1,
      "invoiceAttemptsCount": 0,
      "createDate": "2026-10-04T20:10:02",
      "updateDate": "2026-10-04T20:10:02"
    }
  ],
  "isSuccess": true,
  "message": null
}

GetTicketsByUser

GET /B2B/ReportV1/GetTicketsByUser · int userId

All tickets of a user in your tree, newest first. Same format as GetTicketsByMonth.


User

Association RFCs are managed with the Association APIs (SaveAssociationCSF, ChangeAssociationCSFStatus), not with these.

GetUsersByAssociation

GET /B2B/UserV1/GetUsersByAssociation · int associationId

Users of an association, with their RFCs.

{
  "data": [
    {
      "userId": 10619,
      "createDate": "2026-01-27T21:06:34",
      "updateDate": "2026-01-27T21:06:34",
      "isActive": true,
      "isDeleted": false,
      "name": "Mario",
      "pSurname": "Díaz",
      "mSurname": null,
      "email": "fotofacturas@fotofacturas.ai",
      "phoneCode": "52",
      "phone": "5512345678",
      "isEmailVerified": false,
      "isPhoneVerified": false,
      "isAssociationRoot": false,
      "taxInfos": [
        {
          "userTaxInfoId": 10423,
          "rfc": "XXXX80816UI7",
          "name": "Mario Díaz",
          "csfFile": "https://...",
          "isVerified": true,
          "isActive": true,
          "createDate": "2026-01-27T21:09:23",
          "updateDate": "2026-01-27T21:09:23"
        }
      ]
    }
  ],
  "isSuccess": true,
  "message": null
}
  • The first record always has isAssociationRoot: true: it’s the root user, which represents the association itself. Its taxInfos are the association’s RFCs and it owns the association tickets. Don’t edit or deactivate it with the User APIs.
  • taxInfos: the user’s RFCs, active and disabled. csfFile is a temporary URL.

CreateUser

POST (JSON) /B2B/UserV1/CreateUser

Creates a user in an association of your tree.

{
  "associationId": 42,
  "name": "Nombre",
  "pSurname": "Apellido Paterno",
  "mSurname": "Apellido Materno",
  "email": "usuario@empresa.com",
  "phone": "5512345678",
  "phoneCode": "52"
}
  • Email and phone must be unique in Fotofacturas.
  • phone (at least 8 digits) and phoneCode (at least 2) are stored as digits only. mSurname can be null.
{ "data": 11491, "isSuccess": true, "message": "Usuario creado" }

data is the created userId.

UpdateUser

PUT (JSON) /B2B/UserV1/UpdateUser

Updates a user’s data; it also lets you move them to another association in your tree. If the email or phone changes, its verification is lost.

{
  "userId": 11491,
  "associationId": 42,
  "name": "Nombre",
  "pSurname": "Apellido Paterno",
  "mSurname": "Apellido Materno",
  "email": "usuario@empresa.com",
  "phone": "5512345678",
  "phoneCode": "52"
}

Response: message Usuario actualizado.

ChangeUserStatus

PUT (JSON) /B2B/UserV1/ChangeUserStatus

Activates or deactivates a user. An inactive user can’t upload tickets.

{ "userId": 11491, "isActive": false }

Response: message Usuario desactivado (or activated).

AddUserTaxInfo

POST (multipart/form-data) /B2B/UserV1/AddUserTaxInfo

Registers an RFC for the user by uploading its CSF. Respects multiRfcPerUser. It’s rejected if the user already has that RFC (to update it, use UpdateUserTaxInfo).

FieldTypeRequired
pdfPDF file (CSF)Yes
userIdintYes

Response: message CSF agregada.

UpdateUserTaxInfo

PUT (multipart/form-data) /B2B/UserV1/UpdateUserTaxInfo

Replaces the CSF of one of the user’s RFCs; the new CSF must be for the same RFC. It updates the tax data and reactivates the RFC (if it was disabled, multiRfcPerUser applies).

FieldTypeRequired
pdfPDF file (CSF)Yes
userTaxInfoIdintYes

Response: message CSF actualizada.


Webhook

Reference for the webhook APIs. How they work (events, which webhook is used for each ticket, notification format, signature, retries and troubleshooting) is in Webhooks. You can also configure webhooks and resend events from the Webhooks screen in the business web app; resending is only available there.

GetWebhooks

GET /B2B/WebhookV1/GetWebhooks · int associationId

An association’s webhooks, active and inactive. It never returns the secret.

{
  "data": [
    {
      "webhookSubscriptionId": 7,
      "associationId": 41,
      "eventType": 1,
      "eventName": "ticket.invoiced",
      "url": "https://erp.empresa.com/webhooks/fotofacturas",
      "isActive": true,
      "createDate": "2026-10-13T09:00:00",
      "updateDate": "2026-10-13T09:00:00"
    }
  ],
  "isSuccess": true,
  "message": null
}

SaveWebhook

POST (JSON) /B2B/WebhookV1/SaveWebhook

Creates or updates the webhook for an event on an association (one per event and association).

{ "associationId": 41, "eventType": 1, "url": "https://erp.empresa.com/webhooks/fotofacturas", "isActive": true }
{ "data": { "webhookSubscriptionId": 7, "secret": "whsec_3f8a..." }, "isSuccess": true, "message": "Webhook guardado" }
  • The URL must be HTTPS on port 443, with no username or password, and resolve to public IPs (see Webhooks › URL requirements).
  • If the webhook already existed, url and isActive are updated and the secret doesn’t change.
  • secret is only returned when the webhook is created (on update it’s null). If you lose it, use RegenerateWebhookSecret.

RegenerateWebhookSecret

POST (JSON) /B2B/WebhookV1/RegenerateWebhookSecret

Generates a new secret; the previous one stops being valid immediately.

{ "webhookSubscriptionId": 7 }
{ "data": "whsec_9b1c...", "isSuccess": true, "message": "Secreto regenerado" }

TestWebhook

POST (JSON) /B2B/WebhookV1/TestWebhook

Sends a ping event to the webhook’s URL (even if it’s deactivated) and returns the result. It isn’t retried and isn’t kept in the history.

{ "webhookSubscriptionId": 7 }
{
  "data": { "isDelivered": true, "statusCode": 200, "responseBody": "{\"ok\":true}", "error": null, "durationMs": 184 },
  "isSuccess": true,
  "message": null
}

responseBody is limited to 2000 characters. On timeout or connection error, statusCode is null and error contains the reason.

GetWebhookEvents

GET /B2B/WebhookV1/GetWebhookEvents · startDate, endDate (required, 31 days max) · associationId (the ticket’s association), userTicketId, status (optional)

Event history for a range, newest first. Returns at most maxResults (5000) events; if totalCount is higher, narrow the range or filter.

{
  "data": {
    "totalCount": 1,
    "maxResults": 5000,
    "events": [
      {
        "webhookEventId": 98123,
        "webhookSubscriptionId": 7,
        "associationId": 42,
        "eventType": 1,
        "eventName": "ticket.invoiced",
        "userTicketId": 88254,
        "ticketStatusId": 11,
        "previousTicketStatusId": 5,
        "status": 2,
        "statusName": "DELIVERED",
        "attemptCount": 1,
        "occurredDate": "2026-10-13T10:15:22",
        "nextAttemptDate": null,
        "lastAttemptDate": "2026-10-13T10:15:25",
        "deliveredDate": "2026-10-13T10:15:25",
        "createDate": "2026-10-13T10:15:22"
      }
    ]
  },
  "isSuccess": true,
  "message": null
}

status: 1 PENDING, 2 DELIVERED, 3 FAILED.

GetWebhookEventById

GET /B2B/WebhookV1/GetWebhookEventById · long webhookEventId

Details of an event: the same fields as GetWebhookEvents, plus the webhook’s current URL, the body sent and each attempt.

{
  "url": "https://erp.empresa.com/webhooks/fotofacturas",
  "payload": { "eventId": 98123, "eventType": "ticket.invoiced", "occurredAt": "...", "data": { ... } },
  "attempts": [
    { "attemptNumber": 1, "statusCode": 500, "responseBody": "Internal Server Error", "error": null, "durationMs": 230, "createDate": "2026-10-13T10:15:25" },
    { "attemptNumber": 2, "statusCode": 200, "responseBody": "{\"ok\":true}", "error": null, "durationMs": 140, "createDate": "2026-10-13T10:16:25" }
  ]
}

payload is null if the event hasn’t been sent yet.

The error messages for these APIs are in Webhooks › Troubleshooting.