> 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.

# Agregar items a una cuenta abierta

POST https://app.recurrente.com/api/service_tabs/{service_tab_id}/items
Content-Type: application/json

Agrega uno o más consumos a la cuenta. Cada item queda registrado por
separado en el feed de actividad, igual que la cuenta que ve el cliente.
La respuesta trae la cuenta completa con su nuevo total.

Un item nombra algo que ya vendes (`price_id` o `product_id`) o se define
solo, con `name` y `amount_in_cents`. Son las mismas tres formas que
aceptan los items de un checkout. Los precios recurrentes no se aceptan:
una cuenta abierta solo cobra productos de pago único.


Reference: https://docs.recurrente.com/referencia-api/api-reference/service-tabs/add-service-tab-items

## 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

### Path parameters

- `service_tab_id` (string, required) — ID de la cuenta abierta

### Body (application/json)

This endpoint expects an object.

- `items` (list of ServiceTabsServiceTabIdItemsPostRequestBodyContentApplicationJsonSchemaItemsItems, required)

## Response

### 201

Items agregados

- `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

No se enviaron items, no son válidos para una cuenta abierta, o la cuenta ya está cerrada

- `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

### ServiceTabsServiceTabIdItemsPostRequestBodyContentApplicationJsonSchemaItemsItems

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
{
  "items": [
    {}
  ]
}
```

**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/service_tab_id/items"

payload = { "items": [{}] }
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/service_tab_id/items';
const options = {
  method: 'POST',
  headers: {'X-SECRET-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"items":[{}]}'
};

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/service_tab_id/items"

	payload := strings.NewReader("{\n  \"items\": [\n    {}\n  ]\n}")

	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/service_tab_id/items")

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 = "{\n  \"items\": [\n    {}\n  ]\n}"

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/service_tab_id/items")
  .header("X-SECRET-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"items\": [\n    {}\n  ]\n}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://app.recurrente.com/api/service_tabs/service_tab_id/items', [
  'body' => '{
  "items": [
    {}
  ]
}',
  '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/service_tab_id/items");
var request = new RestRequest(Method.POST);
request.AddHeader("X-SECRET-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"items\": [\n    {}\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-SECRET-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = ["items": [[]]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://app.recurrente.com/api/service_tabs/service_tab_id/items")! 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()
```