# Quickstart: your first Argentine invoice by API

This guide takes a developer or an AI agent from zero to an electronic invoice with a CAE (the ARCA authorization code), then to webhooks and production. The base URL is https://api.yo-facturo.com/api/v1.

## Two ways in

Pick one. The **REST API** with a token (`yf_test_*` or `yf_live_*`) gives full control, including invoicing, and is available on the Premium plan and during the free trial. The **MCP server** gives conversational access from Claude, ChatGPT or Cursor with an OAuth sign-in and no token to copy; it is available from the Basic plan. The last section covers MCP.

## 1. Get a sandbox key

In the panel go to Settings, Integrations, API Tokens and create a token in test mode. A `yf_test_*` token works on an isolated sandbox database and never touches real data. The token is shown once, so store it right away.

For this guide the token needs the scopes `invoices:write`, `invoices:read`, `webhooks:write` and `webhooks:read`, plus `accounting:write` if you want to switch the ARCA environment. Send the key in either header; `X-API-Key` wins if both are present.

```
X-API-Key: yf_test_xxxxxxxx
Authorization: Bearer yf_test_xxxxxxxx
```

Limits are 10 requests per second, 100 per minute, 3,000 per hour and 20,000 per day for the whole account, shared by all tokens. Invoice creation and authorization are further limited to 30 per 5 minutes.

## 2. Issue an invoice in the sandbox

With a test key, `POST /bill/receipts/invoice/` never calls ARCA. It stores a simulated invoice with `simulated: true`, `livemode: false` and a synthetic CAE, which is what you want while building the integration. Prices in `items` are final, IVA included.

Always send an `Idempotency-Key`. Repeating the same key and body replays the first response and adds the header `Idempotency-Replayed: true`; the same key with a different body returns 422.

```bash
curl -s -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/invoice/" \
  -H "X-API-Key: $YF_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "punto_venta": 1,
    "concepto": 1,
    "tipo_comprobante": 6,
    "tipo_documento": 99,
    "numero_documento": "0",
    "razon_social": "Consumidor Final",
    "condicion_iva_receptor_id": 5,
    "items": [{"descripcion": "Test item", "cantidad": 1, "precio_unitario": 1000}]
  }'
```

The response has the shape `{"success": true, "data": {"id": "...", "cae": "...", "simulated": true, "livemode": false}}`. Read `data.cae`. The same call in Python:

```python
import os, uuid, requests

BASE = "https://api.yo-facturo.com/api/v1"
H = {"X-API-Key": os.environ["YF_TEST_KEY"]}

body = {
    "punto_venta": 1, "concepto": 1, "tipo_comprobante": 6,
    "tipo_documento": 99, "numero_documento": "0",
    "razon_social": "Consumidor Final", "condicion_iva_receptor_id": 5,
    "items": [{"descripcion": "Test item", "cantidad": 1, "precio_unitario": 1000}],
}
r = requests.post(f"{BASE}/bill/receipts/invoice/", json=body,
                  headers={**H, "Idempotency-Key": str(uuid.uuid4())}, timeout=30)
r.raise_for_status()
invoice = r.json()["data"]
print(invoice["id"], invoice.get("cae"), invoice.get("simulated"))
```

And in Node.js 18 or later:

```js
const BASE = "https://api.yo-facturo.com/api/v1";
const res = await fetch(`${BASE}/bill/receipts/invoice/`, {
  method: "POST",
  headers: {
    "X-API-Key": process.env.YF_TEST_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    punto_venta: 1, concepto: 1, tipo_comprobante: 6,
    tipo_documento: 99, numero_documento: "0",
    razon_social: "Consumidor Final", condicion_iva_receptor_id: 5,
    items: [{ descripcion: "Test item", cantidad: 1, precio_unitario: 1000 }],
  }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const { data } = await res.json();
console.log(data.id, data.cae, data.simulated);
```

You can wipe the sandbox at any time with `POST /sandbox/reset/`. It only works with a test key; a live key gets 403.

## 3. Get a real homologation CAE from ARCA

The sandbox CAE is synthetic. To get a CAE issued by ARCA's homologation web services, use a **live** key on an account switched to the testing environment. In testing you do not need your own certificate or delegation: the platform's homologation CUIT and certificate are used for you.

```bash
# 1) switch the account to ARCA homologation (scope accounting:write)
curl -s -X POST "https://api.yo-facturo.com/api/v1/bill/tax_info/afip-environment/" \
  -H "X-API-Key: $YF_LIVE_KEY" -H "Content-Type: application/json" \
  -d '{"environment": "testing"}'

# 2) create the invoice as a draft. "ambiente" is read from every request body
#    and defaults to "production": always send "testing" while you test.
curl -s -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/invoice/" \
  -H "X-API-Key: $YF_LIVE_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"ambiente": "testing", "autorizar": false, "punto_venta": 1, "concepto": 1,
       "tipo_comprobante": 6, "tipo_documento": 99, "numero_documento": "0",
       "razon_social": "Consumidor Final", "condicion_iva_receptor_id": 5,
       "items": [{"descripcion": "Homologation test", "cantidad": 1, "precio_unitario": 1000}]}'

# 3) authorize it: synchronous, returns the CAE or ARCA's rejection reason in a 400
curl -s -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/authorize/<id>/" \
  -H "X-API-Key: $YF_LIVE_KEY" -H "Content-Type: application/json" \
  -d '{"ambiente": "testing"}'
```

The `data.cae` of the authorize response is the homologation CAE. Why two steps: with `"autorizar": true` in step 2 the authorization runs in the background and the response carries `authorization_pending: true` with no CAE yet, so you would have to poll `GET /bill/receipts/{id}/`. The two-step flow is synchronous and surfaces the rejection reason, which is what an agent needs to correct itself.

One gotcha: the invoice number is reserved at creation, not at authorization. If ARCA rejects a draft, later invoices can fail with ARCA error 10016 until you run `POST /bill/receipts/doctor/` with `{"apply": true}`.

## 4. Subscribe to a webhook

Register an endpoint with the scope `webhooks:write`. The signing secret is returned once.

```bash
curl -s -X POST "https://api.yo-facturo.com/api/v1/webhooks/endpoints" \
  -H "X-API-Key: $YF_LIVE_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/yf-webhook", "events": ["invoice.authorized"], "description": "agent"}'
# -> 201 {"data": {"id": "...", "secret": "whsec_...", "events": [...], "mode": "live"}}
```

Deliveries are `POST` requests with the headers `X-YF-Signature`, `X-YF-Event` and `X-YF-Delivery`. Verify the signature before trusting the body; the webhooks guide has the format, the verification code and the retry schedule. Reply with a 2xx within 10 seconds.

## 5. Go live

1. **Delegate the invoicing service.** Production needs the taxpayer to delegate the electronic invoicing service (wsfe) to the platform CUIT in ARCA's portal; the Spanish guide [Delegar el web service de facturación](https://yo-facturo.com/dev/arca/delegar-web-service-facturacion/) walks through it. Confirm it with `POST /bill/tax_info/verify-delegation/` and the body `{"cuit": "..."}`.
2. **Switch the environment back** with `POST /bill/tax_info/afip-environment/` and `{"environment": "production"}`. Accounts flagged as testing by an admin cannot do this themselves.
3. **Drop `"ambiente": "testing"`** from your requests, since the default is production, and use a `yf_live_*` key. Keep the `Idempotency-Key` on every write.
4. **Platforms that invoice for many CUITs** use partner accounts, which register each taxpayer with `POST /partner/taxpayers/` and issue A or B for registered taxpayers and C for monotributo or exempt ones. Partner access is granted by contract.

## Connect an AI assistant over MCP

The MCP server lives at `https://api.yo-facturo.com/api/v1/mcp`. It uses the Streamable HTTP transport and OAuth 2.1 with PKCE and dynamic client registration, so there is no token to paste.

1. **Add the server.** In Claude Code run `claude mcp add --transport http yofacturo https://api.yo-facturo.com/api/v1/mcp`. In Claude Desktop or claude.ai add a custom connector with the same URL. In ChatGPT add a custom MCP connector in developer mode. In Cursor add `{"mcpServers": {"yofacturo": {"url": "https://api.yo-facturo.com/api/v1/mcp"}}}` to `~/.cursor/mcp.json`.
2. **Authorize.** The client opens your browser; sign in to YoFacturo and approve `yofacturo:read`, `yofacturo:write` or both. A read-only connection can never modify anything.
3. **Ask.** For example, "list this month's sales" or "create a customer named ACME". Revoke a connection any time in the panel under Settings, Integrations, MCP server.

More on connecting each client is in the MCP guide.

## Keep reading

- [Argentina e-invoicing API overview](https://yo-facturo.com/en/argentina-e-invoicing-api/)
- [OpenAPI specification (JSON)](https://api.yo-facturo.com/api/v1/openapi.json)
- [Spanish version](https://yo-facturo.com/dev/quickstart/)

Source: https://yo-facturo.com/en/dev/quickstart/
