Import managed accounts from a file
Scopeaccounts:writeProvisions 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_createdis how many accounts this call actually created. - Declarative and re-runnable: each pass applies only what is missing — an
external_refyou 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 byexternal_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 thatGET /v1/templates/customer-importdescribes, 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, inown_company_customers, and stays out ofstatistics.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.
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.
Keys are prefixed beel_sk_, and each one carries the scopes it was created with: a key
short of the scope an operation needs is answered 403. The scope an operation requires
is shown next to its title, and the full catalogue lives in the Scopes reference.
Keys are created from the BeeL dashboard. They are secret credentials: do not share them or commit them to source control.
In: header
Header Parameters
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.
^[a-zA-Z0-9_-]+$length <= 255CSV with one managed account per row, in the format of GET /v1/templates/account-import: UTF-8, header names matched case-insensitively, and ,, ; or tab accepted as separator.
The header row does not have to be the first line. A spreadsheet almost never starts on it — there is a title, sometimes a blank line — and that preamble travels ahead of the data when the sheet is exported. Everything before the first line carrying the required columns is ignored, within the first 20 lines. Reported row_numbers still count from the top of the file, so they match what you see when you open it.
Optional CSV of customers to apply to every account of this import, in the format of GET /v1/templates/customer-import. Idempotent by tax id. Its preamble is skipped the same way — it comes out of the same spreadsheet.
Settings that apply to every row of the file. They live here and not as columns because an agency onboards all of its managed accounts the same way: a column nobody varies is a column everybody mistypes.
Travels as an application/json part named options inside the multipart body — send it with its own Content-Type: application/json (-F 'options=…;type=application/json' in curl). Omit the part entirely to take the defaults.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://app.beel.es/api/v1/accounts/imports" \ -H "Idempotency-Key: string" \ -F accounts_file="string"{
"success": true,
"data": {
"metadata": {
"is_dry_run": true,
"total_rows": 19,
"processing_time_ms": 18400,
"accounts_filename": "string",
"customers_filename": "string",
"environment": "PROD"
},
"accounts_validation": [
{
"row_number": 2,
"external_ref": "string",
"nif": "string",
"status": "VALID",
"live_activation_verdict": "CHECKOUT_REQUIRED",
"account": {
"person_id": "087e858e-473c-4f50-b5b0-c1df6c021550",
"account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
"status": "PROVISIONED",
"claim_token": "string",
"claim_url": "http://example.com",
"claim_link_already_issued": false,
"company_id": "b2e6a1c3-1a5e-44ae-a8fd-81f76fd715cf"
},
"series": [
{
"document_type": "UNASSIGNED",
"action": "ALREADY_EXISTS",
"code": "string",
"format": "string"
}
],
"customers": {
"created": 3,
"already_existed": 37,
"failed": 0
},
"errors": [
{
"code": "LIVE_ACTIVATION_NOT_ENTITLED",
"column": "direccion_codigo_postal",
"value": "string",
"message": "string"
}
],
"warnings": [
{
"code": "LIVE_ACTIVATION_NOT_ENTITLED",
"column": "direccion_codigo_postal",
"value": "string",
"message": "string"
}
]
}
],
"customers_source": {
"total_rows": 42,
"valid": 40,
"invalid": 2,
"rejected": [
{
"row_number": 0,
"nif": "string",
"legal_name": "string",
"errors": [
{
"code": "LIVE_ACTIVATION_NOT_ENTITLED",
"column": "direccion_codigo_postal",
"value": "string",
"message": "string"
}
]
}
]
},
"own_company_customers": {
"created": 3,
"already_existed": 37,
"failed": 0
},
"statistics": {
"total_rows": 19,
"valid": 11,
"with_warnings": 1,
"already_existed": 6,
"blocked": 1,
"with_errors": 0,
"importable": 12,
"accounts_created": 12,
"live_activations_created": 12,
"live_activations_pending": 0,
"series_created": 24,
"customers_created": 24
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication is required to access this resource"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "FILE_TOO_LARGE",
"message": "The file exceeds the maximum allowed size of 5.0 MB.",
"details": {
"max_size_bytes": "5242880",
"max_size_formatted": "5.0 MB"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again in 60 seconds."
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "UNSUPPORTED_MEDIA_TYPE",
"message": "Unsupported media type: text/plain. Supported: application/json"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Preview an import of managed accounts POST
Reads the same files as `POST /v1/accounts/imports` and answers the same shape without writing anything: no account is provisioned, no NIF is switched on, no series and no customer are created, and nothing is billed. - **Result shape:** `metadata.is_dry_run` is `true`, every write counter in `statistics` is `0`, and `statistics.importable` is what a real import would create. - **Per row:** it resolves the row's own data — including the repairs a spreadsheet export needs, which `accounts_file` describes — whether the tax id is in the AEAT register, whether the `external_ref` is already an account of yours, and the Live activation verdict that decides whether the import would execute the row at all. - **`statistics.live_activations_pending`:** read it before importing. Every NIF switched on in Live adds an item to your subscription, and this is the only place to see the total before it is charged. - **The customers file** is checked once for the whole import: whether a customer is new to a given account depends on the account, and that only shows up when the import runs.
List the accounts you provisioned GET
Returns the accounts you provisioned, newest first. Each carries its lifecycle `status` (`PROVISIONED` → `CLAIMED` → `ACTIVE`), the `access_level` you hold over it and the state of its claim link. - **`status`:** narrows the list to one lifecycle stage. - **`external_ref`:** looks an account up by the reference you assigned when provisioning it; returns the 0..1 matching accounts. **Cursor pagination.** This collection pages by `cursor`/`next_cursor` instead of by `page`, so it carries no `pagination` block. That is a documented variant of pagination, not a different envelope: the collection still travels under a named key inside `data`. Keep asking with the `next_cursor` of the previous response until it comes back `null`.