> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.recurrente.com/referencia-api/api-reference/service-tabs/create-service-tab/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.recurrente.com/_mcp/server. # Abrir una cuenta POST https://app.recurrente.com/api/service_tabs Content-Type: application/json Abre una cuenta para un cliente. Identifica al cliente con `customer_id`, o con `phone` y `display_name` para crearlo si no existe. El compromiso de apertura se infiere de lo que envíes: `items` abre la cuenta ya con esos consumos, `estimated_amount` la abre con un monto estimado sin items, y si no envías ninguno la cuenta queda vacía y le agregas items después. La estrategia de cobro es siempre `card_on_file`: al cerrar se cobra la tarjeta guardada del cliente (o le entregas un link de pago). La preautorización con hold solo se puede activar desde el dashboard, porque requiere una autorización real del procesador. Usa un `Idempotency-Key` único por cuenta abierta. Reference: https://docs.recurrente.com/referencia-api/api-reference/service-tabs/create-service-tab ## Authentication - `X-SECRET-KEY` header (required) — Tu clave secreta de API. Una llave de cuenta (`sk_live_...`, `sk_test_...`) opera sobre su propia cuenta y, con `X-ACCOUNT-ID`, sobre sus cuentas conectadas. La llave también fija el ambiente: una `sk_test_` solo lista y resuelve objetos de prueba (`live_mode: false`) y una `sk_live_` solo objetos reales. Un ID del otro ambiente responde `404` (`401` en `/customers`). Una llave de organización (`sk_org_live_...`, `sk_org_test_...`) alcanza todas las cuentas de una organización y solo sirve para leer: saldos, movimientos y reportes. Para operar sobre una cuenta debe nombrarla con `X-ACCOUNT-ID`; omitirlo en una lectura devuelve todas las cuentas de la organización. Cualquier otro endpoint responde `403` con `code: organization_key_unsupported`. ## Request ### Body (application/json) This endpoint expects an object. - `customer_id` (string, optional) — ID del cliente. Si lo omites, se resuelve o crea con `phone` y `display_name`. - `display_name` (string, optional) — Nombre con el que identificas la cuenta (mesa, cliente, cuarto) - `phone` (string, optional) — Teléfono del cliente - `currency` (string, optional) — Moneda de la cuenta. Por defecto, la moneda principal de tu cuenta. - `estimated_amount` (string, optional) — Monto estimado de consumo, en unidades de la moneda (no centavos) - `payment_method_id` (string, optional) — Método de pago guardado del cliente que se cobrará al cerrar - `items` (list of ServiceTabsPostRequestBodyContentApplicationJsonSchemaItemsItems, optional) — Consumos con los que abre la cuenta ## Response ### 201 Cuenta abierta - `id` (string, optional) — ID de la cuenta abierta - `status` (enum, optional) — Estado de la cuenta. `open` acepta items; `closing` ya está cerrada y espera el pago; `paid` se cobró; `voided` se anuló sin cobrar; `abandoned` se dejó vencer. - Allowed values: `open`, `closing`, `paid`, `voided`, `abandoned` - `display_name` (string, optional) — Nombre con el que el comercio identifica la cuenta (mesa, cliente, cuarto) - `phone` (string, optional, nullable) — Teléfono del cliente al momento de abrir la cuenta - `customer_id` (string, optional) — ID del cliente dueño de la cuenta - `currency` (string, optional) — Moneda de la cuenta - `total_in_cents` (integer, optional) — Total consumido hasta ahora, en centavos - `payment_strategy` (enum, optional) — `card_on_file` cobra al cerrar la tarjeta guardada del cliente. `preauthorization` mantiene un hold del monto estimado y solo se puede activar desde el dashboard, porque requiere una autorización real del procesador. - Allowed values: `card_on_file`, `preauthorization` - `opening_commitment` (enum, optional) — Qué se comprometió al abrir la cuenta. Se infiere de la solicitud: `products` si mandaste `items`, `manual_amount` si mandaste `estimated_amount`, `none` si no mandaste ninguno. - Allowed values: `none`, `manual_amount`, `product`, `products` - `items` (list of ServiceTabItem, optional) — Líneas vigentes de la cuenta (los items anulados no aparecen) - `authorized_amount_in_cents` (integer, optional) — Monto preautorizado con hold sobre la tarjeta, en centavos. `0` cuando no hay preautorización. - `authorization_expires_at` (datetime, optional, nullable) — Momento en que expira el hold de la preautorización - `checkout_url` (string, optional, nullable) — Checkout que cierra la cuenta; pagarlo la marca como `paid`. Solo aparece mientras la cuenta se está cobrando (`closing` o `paid`): si un cobro con tarjeta se declina, la cuenta se reabre y el checkout de ese intento no se publica, porque el cliente no podría pagarlo. - `opened_at` (datetime, optional) — Momento en que se abrió la cuenta - `closed_at` (datetime, optional, nullable) — Momento en que se cerró la cuenta - `created_at` (datetime, optional) ## Errors ### 422 Unprocessable Entity Error Un item no nombra nada cobrable, o no es válido para una cuenta abierta - `message` (string, optional) — Mensaje de error - `code` (string, optional) — Código estable legible por máquinas, cuando aplica - `refund_id` (string, optional) — ID del reembolso relacionado cuando aplica - `endpoint` (string, optional) — Acción de API rechazada, cuando aplica - `errors` (ErrorErrors, optional) — Detalles de errores por campo ## Types ### ServiceTabsPostRequestBodyContentApplicationJsonSchemaItemsItems Nombra algo que ya vendes (`price_id` o `product_id`) o define el consumo en la misma llamada con `name` y `amount_in_cents` — el cobro manual de un descorche. - `price_id` (string, optional) — Precio existente a cobrar - `product_id` (string, optional) — Producto existente; se cobra su precio vigente - `name` (string, optional) — Nombre del cobro manual, cuando no mandas `price_id` ni `product_id` - `amount_in_cents` (integer, optional) — Monto del cobro manual, en centavos - `currency` (string, optional) — Moneda del cobro manual. Por defecto, la de la cuenta abierta. Una cuenta no puede mezclar monedas: un item en otra moneda se rechaza con `422`. - `quantity` (integer, optional, default: 1) — Entero mayor a 0. Un lote con un item inválido no agrega ninguno. ### ServiceTabItem - `id` (string, optional) — ID del item, firmado y estable mientras el item exista - `price_id` (string, optional) — ID del precio cobrado en esta línea - `name` (string, optional) — Nombre del producto - `quantity` (integer, optional) — Cantidad - `amount_in_cents` (integer, optional) — Total de la línea (precio × cantidad), en centavos - `currency` (string, optional) — Moneda de la línea ### ErrorErrors Detalles de errores por campo ## Examples **Request** ```json {} ``` **Response** ```json { "id": "st_du7dnfwt", "status": "open", "display_name": "Mesa 4", "phone": "+50255551234", "customer_id": "cus_3jfrywsf", "currency": "GTQ", "total_in_cents": 12500, "payment_strategy": "card_on_file", "opening_commitment": "products", "items": [ { "id": "it_eyJfcmFpbHMiOnsi", "price_id": "price_hguxwfto", "name": "Cerveza", "quantity": 2, "amount_in_cents": 5000, "currency": "GTQ" } ], "authorized_amount_in_cents": 0, "authorization_expires_at": "2024-01-15T09:30:00Z", "checkout_url": "https://app.recurrente.com/checkout-session/ch_ccydxmvm8fvhmxsn", "opened_at": "2024-01-15T09:30:00Z", "closed_at": "2024-01-15T09:30:00Z", "created_at": "2024-01-15T09:30:00Z" } ``` **SDK Code** ```python import requests url = "https://app.recurrente.com/api/service_tabs" payload = {} headers = { "X-SECRET-KEY": "", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` ```javascript const url = 'https://app.recurrente.com/api/service_tabs'; const options = { method: 'POST', headers: {'X-SECRET-KEY': '', 'Content-Type': 'application/json'}, body: '{}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://app.recurrente.com/api/service_tabs" payload := strings.NewReader("{}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("X-SECRET-KEY", "") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://app.recurrente.com/api/service_tabs") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["X-SECRET-KEY"] = '' request["Content-Type"] = 'application/json' request.body = "{}" response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://app.recurrente.com/api/service_tabs") .header("X-SECRET-KEY", "") .header("Content-Type", "application/json") .body("{}") .asString(); ``` ```php request('POST', 'https://app.recurrente.com/api/service_tabs', [ 'body' => '{}', 'headers' => [ 'Content-Type' => 'application/json', 'X-SECRET-KEY' => '', ], ]); echo $response->getBody(); ``` ```csharp using RestSharp; var client = new RestClient("https://app.recurrente.com/api/service_tabs"); var request = new RestRequest(Method.POST); request.AddHeader("X-SECRET-KEY", ""); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let headers = [ "X-SECRET-KEY": "", "Content-Type": "application/json" ] let parameters = [] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://app.recurrente.com/api/service_tabs")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ```