> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.recurrente.com/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": "<apiKey>",
    "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': '<apiKey>', '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", "<apiKey>")
	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"] = '<apiKey>'
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<String> response = Unirest.post("https://app.recurrente.com/api/service_tabs")
  .header("X-SECRET-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://app.recurrente.com/api/service_tabs', [
  'body' => '{}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-SECRET-KEY' => '<apiKey>',
  ],
]);

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", "<apiKey>");
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": "<apiKey>",
  "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()
```