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.
The file is declarative and the import is 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.
Two files, one act. accounts_file describes the accounts, one per row.
customers_file is optional and holds a list of customers applied to every account of
the import, new and pre-existing alike. It is what makes both directions cheap: 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.
Live activation is part of the act, and it never charges you by surprise. 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: chaining N payments inside a file upload would turn a declarative import into a
payment flow with intermediate states nobody can recover from. Settle your billing once and
re-upload.
Not atomic. Each row is processed and reported independently: a row that fails leaves
the rows already provisioned in place. Read statistics.accounts_created to know how many
accounts this call actually created.
Claim tokens are shown once. Each newly provisioned row carries account.claim_token
and account.claim_url in this response and nowhere else. Persist them before discarding
the response; a lost token is re-issued with POST /v1/accounts/{account_id}/claim-tokens.
Limits: 5 MB per file; 100 rows in the accounts file and 1,000 in the customers file. The accounts cap is far below the customers one because an account row is an order of magnitude heavier — a provision, a census lookup, a Live activation, its series and its customers — and the answer is synchronous. A larger population is imported in passes, which costs nothing: the file is declarative.
Idempotency: Idempotency-Key is required. The real guarantee, though, is in the data:
rows are idempotent by external_ref, so the same file uploaded twice creates nothing twice
even under a different key.
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.
Requires the accounts:write scope and the managed-accounts capability on your account.
API Key authentication.
Format: Authorization: Bearer beel_sk_<key>
Scopes: API Keys use the same scopes as OAuth2 tokens. Each key is created with
specific scopes that limit which endpoints it can access. The required scope for each
endpoint is documented in the operation's security section under OAuth2.
Obtaining Keys: API Keys are managed from the BeeL dashboard
Security: API Keys are secret credentials. Do not share them or store them in source code
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
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",
"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"
}
]
}
]
},
"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": "La factura debe tener al menos una línea",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required"
},
"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": "La factura debe tener al menos una línea",
"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": "La factura debe tener al menos una línea",
"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": "La factura debe tener al menos una línea",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "FILE_TOO_LARGE",
"message": "The file exceeds the maximum allowed size"
},
"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": "La factura debe tener al menos una línea",
"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"
}
}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. What it resolves per row: the row's own data — including the repairs a spreadsheet export needs, such as a title row ahead of the headers, a postal code that lost its leading zero, an IBAN copied with the spaces of a bank statement, or a number Excel saved as `28031.0` — 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. `metadata.is_dry_run` is `true` and every write counter in `statistics` is `0`; `statistics.importable` is what a real import would create. **Read `statistics.live_activations_pending` 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, with lifecycle status GET
Returns the accounts you provisioned, each with its lifecycle `status` (`PROVISIONED` → `CLAIMED` → `ACTIVE`) and the access you hold. Newest first, cursor-paginated. Filter by `status`, or look one up by `external_ref` (your own id) — an `external_ref` that matches nothing returns an empty list, never an error. Requires the `accounts:read` scope.