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>
| Environment | Base URL | Token |
|---|---|---|
| Production | https://api.fotofacturas.ai/B2B/<Controller>/<API> | Business web app, under API y plan. |
| Test | https://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):
| Field | Type | Description |
|---|---|---|
isSuccess | boolean | Whether the operation succeeded. |
message | string | null | Result message; on errors, the reason. |
data | object | null | Requested 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
| HTTP | When | message |
|---|---|---|
| 200 | Success or a validation or business error (invalid data, resource outside your tree, plan limit, API without permission…). | The error reason. |
| 400 | Malformed JSON, wrong data type or a required field is missing. | Datos inválidos en: userId, rfc or Datos inválidos en la petición. |
| 401 | A problem with the token (see the next table). | The reason. |
| 429 | More than 500 requests per minute from the same IP. | Demasiadas peticiones. Por favor, intenta más tarde. |
| 500 | Unexpected error. | Error inesperado en el sistema |
Authentication errors (HTTP 401):
| Message | Cause |
|---|---|
Falta header de autorización | Authorization wasn’t sent. |
Formato inválido del header de autorización | The header doesn’t start with Bearer . |
Falta el token en el header de autorización | There’s no token after Bearer. |
Token no válido | The token doesn’t exist or the account is inactive. |
Usuario no autorizado para usar este servicio | The token isn’t from a B2B user. |
Servicio no disponible, reintente en un minuto | The service is starting up. |
Common errors (HTTP 200, isSuccess: false):
| Message | Cause |
|---|---|
No estas autorizado para usar esta API | Your account doesn’t have that API’s permission. |
Por favor reintente en unos momentos | The service is starting up. |
La asociación no le pertenece / La asociación no te pertenece | The association isn’t in your tree. |
El usuario no pertenece a tus asociaciones / Este usuario no pertenece a su asociación | The user isn’t in your tree. |
El usuario no existe / La asociación no existe / El ticket no existe | The id doesn’t exist. |
El usuario no está activo | A ticket was uploaded for an inactive user. |
El RFC es obligatorio / El RFC {rfc} no es válido | The RFC is missing or its format isn’t valid. |
El RFC {rfc} no está registrado | The RFC isn’t among the ones the user or association can use. |
El RFC {rfc} está deshabilitado | The RFC is disabled. |
El plan solo permite {n} RFCs en la asociación / El plan solo permite {n} RFCs por usuario | The 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 fin | Missing or invalid date range. |
El mes debe estar entre 1 y 12 / Año inválido / No se puede consultar un mes futuro | Invalid month or year. |
Formats
- Input:
GET: query params.POST/PUTwithout files: JSON.- APIs with files (CSF, logos, ticket image):
multipart/form-data. For several files in one field, repeat the key (for exampleCSFs); nested fields use a dot (for exampleConfig.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 asGetUsersByAssociation.rfcs: the association’s RFCs, active and disabled.csfFileis a temporary URL.config: always present; fields that aren’t configured return their default value. Logos are public URLs ornull.
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.
| Field | Type | Required | Description |
|---|---|---|---|
Name | string | Yes | Association name. |
ParentId | int | Yes | Parent association (in your tree). |
CSFs | PDF file(s) | No | The association’s CSFs. Repeat the key for several. No repeated RFCs and without exceeding multiRfcPerAssociation. |
Config.* | No | UpdateAssociationConfig 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 }
parentIdis 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.
| Field | Type | Description |
|---|---|---|
AssociationId | int | Required. |
LogoSquare / LogoRectangle | JPG or PNG file | Square / rectangular logo (max. 4 MB). Replaces the previous one. |
CompanyName | string | Company name to display (max. 150 characters). |
DefaultRegimenFiscalId | int | Default tax regime. |
DefaultUsoCfdiId | string | Default CFDI use. Requires a tax regime (the one sent or the saved one) and must be valid for it. |
DefaultTicketPaymentMethodId | string | Default payment method. |
CanChangeRegimenFiscal / CanChangeUsoCfdi / CanChangePaymentMethod | bool | Whether the user can change that value when uploading a ticket. |
StyleRoundness | int | Interface roundness (non-negative). |
StyleSidebarColor / StyleAccentColor / StyleAccentStrongColor | string | Color #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.
| Field | Type | Required |
|---|---|---|
AssociationId | int | Yes |
CSF | PDF file | Yes |
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
}
| Field | Description |
|---|---|
ticketsMonthlyLimit, ticketsMonthlyLimitPerUser, canPictureInvoice, multiRfcPerUser, multiRfcPerAssociation, multiRfcType | Plan limits (see Concepts › Plan and limits). |
ticketsUsed | Tickets in the current month across the whole tree, excluding discarded ones. |
associationsCount | Associations in the tree, including the root. |
associationActiveTaxInfosCount / associationInactiveTaxInfosCount | Active / disabled association RFCs. |
userActiveTaxInfosCount / userInactiveTaxInfosCount | Active / 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 benull.isSuccessCode: whether the code is a success result; can benull.
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>.
| API | Method | Parameters (optional) | Response (data) |
|---|---|---|---|
GetSatProductDivisions | GET | None | [{ "satProductDivisionId", "description" }] |
GetSatProductFamilies | GET | satProductDivisionId | [{ "satProductFamilyId", "description", "satProductDivisionId" }] |
GetSatProductClasses | GET | satProductFamilyId | [{ "satProductClassId", "description", "satProductFamilyId" }] |
GetSatProducts | GET | satProductClassId, 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,pdfUrlandxmlUrlare temporary URLs.pdfUrlandxmlUrlarenullif there’s no invoice.xmlData: data read from the invoice XML (nullif there’s no XML).taxIdentifier/taxName: the RFC and name being invoiced.invoiceMethod: how the invoice was obtained (nullif 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.
| Field | Type | Required | Description |
|---|---|---|---|
UserId | int | Yes | An active user in your tree. |
Image | file | Yes | Photo of the ticket: JPG, PNG, WEBP, HEIC or PDF (max. 10 MB). |
Rfc | string | Yes | RFC to invoice to. It must be registered and active. |
TicketPaymentMethodId | string | Yes | GetTicketPaymentMethods catalog. |
RegimenFiscalId | int | Yes | GetRegimenFiscalAndCFDI catalog. |
UsoCfdiId | string | Yes | Must be valid for the tax regime. |
CardLastFour | string | Conditional | Last 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.
| Field | Type | Required |
|---|---|---|
AssociationId | int | Yes |
Image, Rfc, TicketPaymentMethodId, RegimenFiscalId, UsoCfdiId, CardLastFour | Same 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,ticketsMonthlyLimitis 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
}
| Field | Description |
|---|---|
processedTicketCount | Invoiced tickets (11). |
cancelledTicketCount | Not-invoiceable tickets (10). |
waitingTicketCount | Tickets in progress (any other status). |
totalTicketsUsed | Sum of the three above. |
totalInvoiced | Sum of the amounts of invoiced tickets. |
totalIVATaxInvoiced | totalInvoiced × 16%. |
ticketsMonthlyLimit | The 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. ItstaxInfosare 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.csfFileis 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) andphoneCode(at least 2) are stored as digits only.mSurnamecan benull.
{ "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).
| Field | Type | Required |
|---|---|---|
pdf | PDF file (CSF) | Yes |
userId | int | Yes |
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).
| Field | Type | Required |
|---|---|---|
pdf | PDF file (CSF) | Yes |
userTaxInfoId | int | Yes |
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,
urlandisActiveare updated and the secret doesn’t change. secretis only returned when the webhook is created (on update it’snull). If you lose it, useRegenerateWebhookSecret.
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.