Import managed accounts from a file
Provisions the managed accounts described in an uploaded file, switches each one on in Live, and leaves them ready to invoice. It is the same act as calling POST /v1/accounts once per row and then POST /v1/companies/{company_id}/activations, with the bookkeeping done for you.
Idempotency and re-runs
- Not atomic: each row is processed and reported independently, and a row that fails leaves the rows already provisioned in place. statistics.accounts_created is how many accounts this call actually created.
- Declarative and re-runnable: each pass applies only what is missing — an external_ref you already provisioned is reconciled, not duplicated, and so are its series and its customers. That is the recovery path for anything that went wrong: fix the cause and upload the same file again; there is no resume and no partial state to clean up.
- Idempotency-Key: required, but the real guarantee is in the data. Rows are idempotent by external_ref, so the same file uploaded twice creates nothing twice even under a different key.
- Dry run: to see what this would do without writing anything, use POST /v1/accounts/imports/preview, a separate operation with no effects at all — the import is never governed by a boolean flag.
Files and limits
- accounts_file: describes the accounts, one per row.
- customers_file: optional, and holds a list of customers applied to every account of the import, new and pre-existing alike, so a new managed account is born knowing all the customers and a new customer reaches all the accounts on the next pass. It is the same CSV that GET /v1/templates/customer-import describes, and it is idempotent by tax id.
- options.apply_customers_to_own_company: lands those customers on your own company as well — the one in focus, never one chosen for you. That outcome comes back apart, in own_company_customers, and stays out of statistics.customers_created.
- Limits: 5 MB per file, 100 rows in the accounts file and 1,000 in the customers file. A larger population is imported in passes, which costs nothing because the file is declarative.
Live activation
Live activation is part of the act: every row is weighed against the same verdict the account state publishes, and only rows entitled to Live are executed; the rest come back BLOCKED with the reason. The import never opens a checkout, so it never charges you by surprise: settle your billing once and re-upload.
If your agreement caps the NIFs you can have in Live, each row switched on takes a place, in file order; the rows that no longer fit come back BLOCKED with NIF_LIMIT_REACHED, the same code POST /v1/companies/{company_id}/activations answers with.
Claim tokens
account.claim_token and account.claim_url: each newly provisioned row carries them in this response and nowhere else, so persist them before discarding it. A lost token is re-issued with POST /v1/accounts/{account_id}/claim-tokens.
Re-uploading the file never breaks the links you already handed out: a row whose account is still unclaimed and holds a valid claim link comes back with claim_token and claim_url null and account.claim_link_already_issued: true. Only an account with no valid link left (expired, or never issued) gets a fresh one.
Request
- Base URL: https://app.beel.es/api
- URL: https://app.beel.es/api/v1/accounts/imports
- Auth: HTTP bearer
Headers
Same key as Idempotency-Key above, but required: the operation writes many rows per call, so a retry without a key would import the same file twice. A missing key answers 400 IDEMPOTENCY_KEY_REQUIRED.
Response
The import ran, in the same format as the preview. The status is fixed: it reports that the import ran, not that any particular row succeeded. metadata.is_dry_run is false, and the write counters in statistics are what actually reached the database — they can all be 0 when every row was already there or blocked.
Example response
{
"success": true,
"data": {
"metadata": {
"total_rows": 19,
"processing_time_ms": 18400,
"environment": "PROD"
},
"accounts_validation": [
{
"row_number": 2,
"status": "VALID",
"live_activation_verdict": "CHECKOUT_REQUIRED",
"customers": {
"created": 3,
"already_existed": 37
},
"errors": [
{
"code": "LIVE_ACTIVATION_NOT_ENTITLED",
"column": "direccion_codigo_postal"
}
],
"warnings": [
{
"code": "LIVE_ACTIVATION_NOT_ENTITLED",
"column": "direccion_codigo_postal"
}
]
}
],
"customers_source": {
"total_rows": 42,
"valid": 40,
"invalid": 2,
"rejected": [
{
"errors": [
{
"code": "LIVE_ACTIVATION_NOT_ENTITLED",
"column": "direccion_codigo_postal"
}
]
}
]
},
"own_company_customers": {
"created": 3,
"already_existed": 37
},
"statistics": {
"total_rows": 19,
"valid": 11,
"with_warnings": 1,
"already_existed": 6,
"blocked": 1,
"importable": 12,
"accounts_created": 12,
"live_activations_created": 12,
"series_created": 24,
"customers_created": 24
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Changes
No recorded changes to this operation across all 1 revision of this API.