Skip to content

API & integrations (CRM, Make, n8n)

Connect your CRM or your ERP to Sattotal with API keys: sync customers and suppliers automatically and securely

The Sattotal API lets another program — your CRM, your ERP, Make, n8n, Zapier or your own script — read and create customers and suppliers in your organization without anyone importing a CSV by hand. It is controlled with API keys that an administrator creates in Settings → API, each with its own name and with permissions per resource, and which can be revoked at any time. Here is what it does, how to create a key, how to call the API and how to set up a sync with Make step by step.

Connect your CRM and your automations

Any tool that can make an HTTP request (Make, n8n, Zapier, your own CRM or ERP) can read, create and update customers and suppliers.

Named keys with permissions

One key per integration, and inside each key one permission per resource: you can grant read access to suppliers without granting write access to customers. Up to 10 active keys per organization.

Incremental sync

With the actualizadoDesde parameter you only fetch the customers or suppliers that changed since the last run: ideal for a scenario that runs every few minutes.

Secure by design

The key is shown only once and stored as an irreversible hash. Each key only reaches its own organization's data and can be revoked instantly.

Who can use it

Keys are created and revoked by the organization's administrators. The API is included in the paid plans (Basic, Pro and Enterprise) and during the trial period; on the free plan the screen shows a lock with the option to change plan. A key always acts on behalf of the organization, not a person: it does not inherit any technician's permissions and does not appear as a team member.

How to create a key

1

Go to Settings → API

From the side menu, Settings, «API» card. You will see your organization's keys (active and revoked) and a «How to connect» card with the base URL and an example.

2

Click «New key»

Give it a name that identifies the integration («Store CRM», «Make», «n8n»). If you ever have to revoke it, you will know which one it is.

3

Choose the permissions

Tick the resources the integration needs — Customers, Suppliers or both — and for each one choose «Read only» (look up) or «Read and write» (look up, create, update and archive). An unticked resource is a resource the key cannot reach. Pick the minimum it needs.

4

Copy the key and store it in your tool

The full key (it starts with sat_) is shown only once. Copy it with the button and paste it into Make, n8n or your CRM. If you lose it there is no way to recover it: revoke it and create another.

Settings → API: key list and a freshly created key

Make CRM

sat_Ab3k…x9Zq

Revoke

n8n

sat_Qm7t…p2Lk

Revoked

Key created

sat_Ab3k9Zq2Xv8Lp1Mn4Rt7Wy0Cd5Fg6Hj3Kl8Sx9ZqCopy

This is the only time you will see the full key. If you lose it, revoke it and create a new one.

Key permissions

Read only

Can list and look up that resource. Any attempt to create, change or archive gets a 403 error with code PERMISO_DENEGADO and the missing permission.

Read and write

Besides looking up, it can create, update and archive in that resource. It is the permission a two-way sync needs.

Permissions are per resource

Permissions are per resource, and it already showed: when suppliers were added, the keys that existed — all of them for customers — did NOT gain access to them. It had to be ticked. The same will hold for any resource added later: an integration never sees more than what you granted.

How to authenticate

Send the key with every request, server to server, in the Authorization: Bearer sat_… header. If your tool does not allow authorization headers, the x-api-key header is accepted too. The base URL is your Sattotal URL followed by /api/v1 (it is ready to copy on the Settings → API screen).

GET /api/v1/me
Authorization: Bearer sat_Ab3k9…x9Zq

The API deliberately has no CORS: it is meant for servers and automation tools, not for web pages or apps running in your customers' browser. A key must never reach a browser.

What you can do (endpoints)

Every response has the shape success + data (plus pagination on lists). You can request a customer by its id or by its code (CLI-0007); a supplier, only by its id.

MethodPathWhat it doesPermission
GET/meReturns your organization and the key you are calling with. Use it to «test the connection» in Make or n8n.Any
GET/clientesPaginated list of customers. Filters: busqueda, tipo, activo and actualizadoDesde. Without activo it also returns archived ones.Read
POST/clientesCreates a customer with the same rules as the form (tax id according to your country, no duplicate tax id or email).Write
GET/clientes/{id}Returns one customer by id or by code.Read
PATCH/clientes/{id}Updates only the fields you send. PUT is accepted as a synonym.Write
DELETE/clientes/{id}Archives the customer (activo = false). Nothing is deleted and it can be repeated without error.Write
GET/proveedoresPaginated list of suppliers. Filters: busqueda, activo, tipoProveedor, codigo and actualizadoDesde. Without activo it also returns archived ones.Read
POST/proveedoresCreates a supplier. Only the name is required. If one already exists with the same tax id or the same name, it returns 409 with the id of the existing one.Write
GET/proveedores/{id}Returns a supplier by its id. The code does NOT work here: see the note below.Read
PATCH/proveedores/{id}Updates only the fields you send. The contact list is replaced as a whole. PUT is accepted as a synonym.Write
DELETE/proveedores/{id}Archives the supplier (activo = false). Nothing is deleted and it can be repeated without error.Write
GET/reparacionesPaginated list of tickets, newest check-in first. Filters: cliente, estado, entradaDesde, entradaHasta and actualizadoDesde.Read
GET/reparaciones/{id}Returns one ticket by its id or its ticket number.Read
GET/clientes/{id}/reparacionesA client's tickets (by the client's id or code), with the same filters.Read
GET/clientes/{id}/resumenA summary of the customer's activity: how many tickets they have (in total, open and by status), how much they've been quoted and when they last came in.Read

Customer fields

The fields are the same as in the customer record: nombre and tipo (particular or empresa) are required; nif_cif, apellidos, razonSocial, email, telefono, telefonoSecundario, direccion (calle, numero, piso, codigoPostal, localidad, provincia, pais) and notas are optional. The tax id is stored in upper case and the email in lower case. In responses every key is always present, with null when there is no value, so the field mapping in your tool never breaks. Watch the name: nombre is the first name (or the business name for a company) and the last name goes separately in apellidos, plural.

Fields the API doesn't recognize

If the body includes a key that doesn't exist (for example firstName instead of nombre, or the singular apellido instead of apellidos), the request isn't rejected, but that value isn't saved. So it doesn't go unnoticed, the response when creating or editing a customer includes avisos.camposIgnorados listing those keys; address keys include their path, such as direccion.ciudad. If you see it, check your integration's field mapping. Fields the API itself returns (id, codigo, createdAt…) never trigger it, so you can read a customer, change it and send it back in full.

POST /api/v1/clientes
{ "firstName": "Daniel", "nombre": "Florea", "tipo": "particular" }

201 Created
{
  "success": true,
  "data": { "nombre": "Florea", "apellidos": null, … },
  "avisos": { "camposIgnorados": ["firstName"] }
}

Example: create a customer

Request

POST /api/v1/clientes
Authorization: Bearer sat_Ab3k9…x9Zq
Content-Type: application/json

{
  "nombre": "Daniel",
  "apellidos": "Florea",
  "tipo": "empresa",
  "razonSocial": "Assista Tech SRL",
  "nif_cif": "RO12345678",
  "codigoPersonalizado": "801",
  "email": "daniel@example.com",
  "telefono": "+40 700 000 000",
  "direccion": { "localidad": "București", "pais": "RO" }
}

Response (201)

{
  "success": true,
  "data": {
    "id": "64b0…0001",
    "codigo": "CLI-0042",
    "codigoPersonalizado": "801",
    "codigoVisible": "801",
    "tipo": "empresa",
    "nombre": "Daniel",
    "apellidos": "Florea",
    "razonSocial": "Assista Tech SRL",
    "nif_cif": "RO12345678",
    "email": "daniel@example.com",
    "activo": true,
    "createdAt": "2026-09-18T10:00:00.000Z",
    "updatedAt": "2026-09-18T10:00:00.000Z"
  }
}

Suppliers

Besides customers, the API exposes the supplier catalogue: the same five endpoints, the same error codes and the same incremental sync. It is only the catalogue: purchases and supplier invoices stay where they already are, in your ERP or in Sattotal.

Supplier fields

Only nombre is required. Optional: codigo, cif, email, telefono, telefonoSecundario, web, direccion (a single line, not an object as in customers), ciudad, provincia, codigoPostal, pais, contactos, tipoProveedor (general, producto, servicio, logistica or otro), formaPago, plazoPago (in days; 0 means on delivery), cuentaCliente and notas. Each contact has nombre —required—, cargo, telefono, email and notas; contacts have no identifier of their own.

How contacts are updated

The list you send REPLACES the one that was there. An empty list deletes every contact; omitting the key (or sending null) leaves them untouched. That is what lets you read a supplier, change one field and send the whole thing back without surprises.

Request

POST /api/v1/proveedores
Authorization: Bearer sat_Ab3k9…x9Zq
Content-Type: application/json

{
  "nombre": "Distribuciones Norte",
  "cif": "B12345678",
  "tipoProveedor": "producto",
  "plazoPago": 30,
  "cuentaCliente": "C-4471",
  "contactos": [
    { "nombre": "Ana Ruiz", "cargo": "Ventas", "email": "ana@norte.es" }
  ]
}

Response (201)

{
  "success": true,
  "data": {
    "id": "64b0…0009",
    "codigo": "PROV-004",
    "nombre": "Distribuciones Norte",
    "cif": "B12345678",
    "tipoProveedor": "producto",
    "plazoPago": 30,
    "cuentaCliente": "C-4471",
    "contactos": [
      { "nombre": "Ana Ruiz", "cargo": "Ventas",
        "telefono": null, "email": "ana@norte.es", "notas": null }
    ],
    "excluidoRecargoEquivalencia": false,
    "activo": true,
    "createdAt": "2026-09-19T08:00:00.000Z",
    "updatedAt": "2026-09-19T08:00:00.000Z"
  }
}

Bank details are not in the API

The supplier's IBAN and bank accounts are neither returned nor accepted, on purpose: if a key ever leaked, it would be useless for bank-change fraud, the most common kind with suppliers. cuentaCliente is returned, because that is YOUR customer number at that supplier and it is what an ERP uses to reconcile purchases.

The equivalence surcharge flag is read-only

The excluidoRecargoEquivalencia field is returned and you may send it back with the same value (so you can return the whole object), but changing it through the API returns 400: it decides whether the surcharge applies to your purchases from that supplier, so it moves the taxable base of your purchase invoices. It is changed from the supplier's record. Outside Spain it has no effect at all.

A supplier code does not locate a supplier

Unlike the customer code, the supplier code (PROV-004) is not unique: it can repeat even inside your own shop. That is why GET /proveedores/{id} only accepts the id. If your ERP only stores the code, use the list filter: GET /proveedores?codigo=PROV-004 returns everything that matches and you decide.

Repair tickets (read-only)

The API also lets you read repair tickets: ticket number, status, dates, the device (type, brand, model, serial number, IMEI) and the estimate. It is read-only: tickets are still created and moved between statuses in Sattotal, where the drop-off receipt and the pickup are signed. The key needs the Repairs resource ticked; client or supplier keys can't reach them.

Ticket fields

Each ticket includes numeroFicha, estado, prioridad, ubicacion, averiaDeclarada, diagnostico, the lifecycle dates (check-in, diagnosis and repair start and end, customer notice, pickup and ultimoCambioEstado, when it entered its current status), a client summary (id, codigo, codigoVisible, nombre, apellidos, razonSocial), the device (id, codigo, tipo, marca, modelo, numeroSerie, imei, color), the estimate (numero, total, estado, fechaEnvio and fechaRespuesta, or null if there is none), the assigned technician (name only) and plazoEntregaEstimado, the turnaround time the customer was given on the check-in receipt. Internal notes, signatures, photos, documents and device passwords are never returned.

Request

GET /api/v1/clientes/CLI-0042/reparaciones?estado=reparado
Authorization: Bearer sat_Ab3k9…x9Zq

Response (201)

{
  "success": true,
  "data": [{
    "id": "64b0…0042",
    "numeroFicha": "ORD-2026-0042",
    "estado": "reparado",
    "prioridad": "normal",
    "averiaDeclarada": "No carga",
    "diagnostico": "Conector de carga dañado",
    "fechas": {
      "entrada": "2026-09-20T09:00:00.000Z",
      "finReparacion": "2026-09-22T17:30:00.000Z",
      "entrega": null
    },
    "cliente": { "id": "64b0…0001", "codigoVisible": "801", "nombre": "Daniel" },
    "dispositivo": {
      "tipo": "movil", "marca": "Apple", "modelo": "iPhone 13",
      "numeroSerie": "F2LXX0000", "imei": "356789012345678"
    },
    "presupuesto": { "numero": "PRES-2026-0010", "total": 89.9, "estado": "aprobado" },
    "tecnico": { "nombre": "Andrei Popescu" },
    "plazoEntregaEstimado": "3 días laborables"
  }],
  "pagination": { "total": 1, "page": 1, "limit": 25, "totalPages": 1 }
}

Look up the customer by the number that's calling

If your CRM or phone system pulls up the customer record when a call comes in, use GET /clientes?telefono=<number>. It searches both the main and the secondary phone and ignores how the number is written: spaces, dashes, parentheses and your country code (+1 or 001). So “(555) 123-4567” finds a customer saved as “+1 555.123.4567”. At least 6 digits are required.

Customer summary for your CRM

GET /clientes/{id}/resumen returns, in a single call, what a CRM usually shows on the customer record: totalReparaciones, reparacionesAbiertas (the device is still at the shop), porEstado, totalPresupuestado and totalPresupuestosAprobados (in the shop's currency, which comes in moneda), primeraReparacion, ultimaReparacion and ultimaActividad (the latest change on any of their tickets). It needs the Repairs resource, just like the tickets.

Request

GET /api/v1/clientes?telefono=0722 123 456
→ { "data": [{ "id": "64b0…0001", "codigoVisible": "801", "telefono": "+40 722 123 456", … }] }

GET /api/v1/clientes/801/resumen
{
  "success": true,
  "data": {
    "cliente": { "id": "64b0…0001", "codigoVisible": "801" },
    "moneda": "RON",
    "totalReparaciones": 7,
    "reparacionesAbiertas": 1,
    "porEstado": { "entregado": 6, "en_reparacion": 1 },
    "totalPresupuestosAprobados": 1240.5,
    "ultimaReparacion": "2026-10-02T09:15:00.000Z",
    "ultimaActividad": "2026-10-06T16:40:00.000Z",
    …
  }
}

Sync only what has changed

You do not need to fetch everything on every run. Store the date and time of the last run in your tool and request only the customers or suppliers modified since then with the actualizadoDesde parameter. The response is ordered by modification date ascending with a stable tie-breaker, so you can page through it without skipping records. Archived records are included too (with activo = false), so your CRM can reflect the archive.

GET /api/v1/clientes?actualizadoDesde=2026-09-18T10:00:00Z&limit=100&page=1
Authorization: Bearer sat_Ab3k9…x9Zq
1

First run

Walk GET /clientes with limit=100 and page=1, 2, 3… until totalPages runs out. Save the start time.

2

Following runs

Request GET /clientes?actualizadoDesde=<saved time> and process only what comes back. Save this run's start time again.

3

Link, do not duplicate

Store the Sattotal id next to your CRM record. If creating returns 409 DUPLICADO, the response carries existenteId: link that one instead of creating another.

Pagination and search

Lists accept page (from 1) and limit (25 by default, 100 at most; anything higher is capped at 100). The busqueda field searches name, surname, company name, tax id, email, code and phone.

Error codes and what to do

Every error response carries success: false, a human-readable message and a stable code meant for programs to decide on. These are the ones you may get:

NO_AUTORIZADO

The key is missing, has a format that is not ours or does not exist. Check the Authorization header.

CLAVE_REVOCADA

The key was valid but an administrator revoked it. Create a new one in Settings → API and update it in your tool.

PLAN_REQUERIDO

The organization is on the free plan. The API works again after switching to a paid plan.

PERMISO_DENEGADO

The key lacks the permission for that operation. The response says in ambitoRequerido which one is missing: create a key with that permission.

NO_ENCONTRADO

There is no customer or supplier with that id in your organization. Those of other organizations are never visible, and «does not exist» is not distinguished from «is not yours».

DUPLICADO

A record with that data already exists: tax id or email for customers; tax id, name or code for suppliers. The response carries campo and existenteId so you can link it instead of creating another.

VALIDACION

Some field fails validation (or the JSON is malformed). details lists the field and the reason, just like the form.

IDENTIFICADOR_FISCAL_REQUERIDO

That customer needs a tax id: in your country it is mandatory for the customer type you sent (for example, always for companies).

RATE_LIMIT

Too many requests. Wait the seconds given in the Retry-After header and retry.

API_DESACTIVADA

The API is temporarily disabled for maintenance. Retry later.

Limits

120 requests per minute per key (plenty for a periodic sync; it stops a runaway loop). 10 active keys per organization. Lists return at most 100 records per page. If the organization is on the free plan with its monthly quota used up, creating customers is blocked just like in the app.

Step by step: sync customers with Make

A typical scenario: every 15 minutes, bring the customers and suppliers created or modified in Sattotal into your CRM. In n8n the equivalent uses an HTTP Request node and a Schedule node.

1

Create the key in Sattotal

Settings → API → New key, «Read only» if you will only read, «Read and write» if you will also create customers from the CRM. Copy the key.

2

HTTP module «Make a request»

URL: your base URL + /clientes. Method GET. Authorization header with value Bearer and your key. Tick «Parse response» to work with the JSON.

3

Test the connection

First of all, run one request to /me: if it returns your organization and the key name, authentication is fine.

4

Add the incremental filter

Store the last run date in a Data store or a variable and pass it as actualizadoDesde in the URL. Schedule the scenario every 15 minutes.

5

Map the fields into your CRM

Iterate data[] and map id, codigo, nombre, apellidos, email, telefono, nif_cif and direccion. Store the Sattotal id in your CRM so you update instead of duplicating.

To create customers from the CRM, add another HTTP module with method POST to /clientes and the customer's JSON body. If you get 409, use existenteId to link.

Security good practices

One key per integration

You can revoke one without breaking the others, and the list shows when each was last used.

Never in a browser or a public repository

Keep it in your tool's credential store (Make connections, n8n credentials, environment variables). If it ends up in a repository or a website, consider it leaked.

If it leaks, revoke and create another

Revocation is immediate: the old key starts getting 401 CLAVE_REVOCADA. Update the new one in your tool and you are done.

API specification

The complete technical reference (paths, parameters, schemas and error codes) is published in an open format at /api/v1/openapi.json, no key needed. By default it comes in Spanish; add ?lang= with your language to get it translated (for example /api/v1/openapi.json?lang=en-US, ?lang=en or ?lang=ro). You can import it into Postman, Insomnia, Make or n8n to have every call ready.

Frequently asked questions

I lost the key, can I see it again?

No. It is shown only when created and then stored irreversibly. Revoke it in Settings → API and create another.

Are there webhooks so Sattotal notifies my CRM when something changes?

Not yet. The recommended way is to poll periodically with the actualizadoDesde parameter, which only returns what changed.

What data does the API expose?

Customers and suppliers: list, create, look up, update and archive. Permissions are per resource, so a key created before suppliers existed does not reach them until an administrator ticks it. Plus repair tickets, read-only (status, dates, device and estimate), with their own permission.

Can I delete a customer through the API?

No, only archive it (activo = false), the same as in the app. That applies to customers and suppliers alike. An archived record still appears in the sync so your CRM can reflect it.

Is the API in the free plan?

No. It is included in Basic, Pro and Enterprise and during the trial. On the free plan you can still see and revoke the keys you created.

Only the customer's last name comes through. Why isn't the first name saved?

It's almost always the field mapping: the first name goes in nombre and the last name in apellidos. If your tool sends the first name under another key (firstName, name, apellido…), the API drops it and tells you in avisos.camposIgnorados in the response. Fix the mapping and send the customer again with a PATCH.

Can I find the customer from the number that's calling me?

Yes. Request GET /api/v1/clientes?telefono= with the number exactly as it reaches you: it doesn't matter if it has a country code, spaces or dashes, and the secondary phone is searched too. With their id you can then request their tickets (/clientes/{id}/reparaciones) and their summary (/clientes/{id}/resumen).

Want to try it yourself?

Try Sattotal free with sample data, no credit card required.