> ## Documentation Index
> Fetch the complete documentation index at: https://docs.melso.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Melso MCP: https://mcp.melso.ai/mcp (OAuth; one company per connection). Documentation-search MCP: https://docs.melso.ai/mcp; it does not act on customer accounts.
>
> Use the live tool list and respect company membership. Ask the person to approve the exact invoice or tax return version in the signed-in app. Melso's professionals prepare and file included returns after approval; an AI assistant does not file. A prepared file or an approval is not evidence of filing: only the AEAT's reference and receipt are. Keep credentials out of chat and logs.

# Ejemplos de integración: 303, Verifactu y SL — Examples

> curl and Node samples for a company, modelo 303, approval, the filing receipt, affiliation and a Verifactu invoice.

Samples for the partner REST API at `https://api.melso.ai/v1`. Replace the identifiers with values from your own company. A partner key is a secret for your server: do not put it in a browser or a log.

Each sample is labeled with the same words as [Availability](https://docs.melso.ai/api/availability.md). A **planned** sample matches the published contract and is not callable yet. A **live** sample still needs a partner key (**private beta**). Without one, the API answers `401`.

```bash theme={null}
export MELSO_PARTNER_KEY="mk_test_your_key"
export MELSO_API="https://api.melso.ai/v1"
export COMPANY_ID="00000000-0000-4000-8000-000000000001"
```

## Create a company

**Planned.** `POST /companies` is not callable yet. `setup_request` `register_autonomo` or `incorporate_sl` would ask professionals to register the autónomo or incorporate an S.L. The live way to ask for that work, once the company exists, is a [case](#alta-de-autónomo-y-constituir-una-sl).

```bash theme={null}
curl --fail-with-body "$MELSO_API/companies" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5b0f1c62-3d7e-4a8f-9b1c-2d3e4f5a6b7c" \
  --data-binary @- <<'JSON'
{
  "kind": "company",
  "name": "Talleres Ebro",
  "external_id": "cus_48213",
  "owner": {"name": "Lucía Martín Pérez", "email": "lucia@example.com", "locale": "es"},
  "fiscal_profile": {
    "tax_id": "B12345674",
    "legal_name": "Talleres Ebro S.L.",
    "address": "Calle Mayor 1",
    "postal_code": "50001",
    "city": "Zaragoza",
    "activity_description": "Reparación de vehículos",
    "vat_regime": "general"
  },
  "setup_request": "incorporate_sl",
  "services": ["invoicing", "tax_returns"]
}
JSON
```

```javascript theme={null}
const response = await fetch("https://api.melso.ai/v1/companies", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MELSO_PARTNER_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "5b0f1c62-3d7e-4a8f-9b1c-2d3e4f5a6b7c",
  },
  body: JSON.stringify({
    kind: "company",
    name: "Talleres Ebro",
    external_id: "cus_48213",
    owner: { name: "Lucía Martín Pérez", email: "lucia@example.com", locale: "es" },
    fiscal_profile: {
      tax_id: "B12345674",
      legal_name: "Talleres Ebro S.L.",
      address: "Calle Mayor 1",
      postal_code: "50001",
      city: "Zaragoza",
      activity_description: "Reparación de vehículos",
      vat_regime: "general",
    },
    setup_request: "incorporate_sl",
    services: ["invoicing", "tax_returns"],
  }),
});
if (!response.ok) throw new Error(await response.text());
const company = await response.json();
console.log(company.id);
```

Node.js 22. The call is planned, so a key that exists today is not enough for a `201`.

## Read a calculated modelo 303

**Live** in the app and MCP `taxes`. **Private beta** on REST. Melso calculates the 303 from the company's records. There is no endpoint that you call to "run" the calculation. While the filing window is open, a draft is recalculated as records change.

```bash theme={null}
curl --fail-with-body "$MELSO_API/companies/$COMPANY_ID/tax-returns?model=303" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY"
```

```python theme={null}
import json, os, urllib.request
request = urllib.request.Request(
    os.environ["MELSO_API"] + "/companies/" + os.environ["COMPANY_ID"] + "/tax-returns?model=303",
    headers={"Authorization": "Bearer " + os.environ["MELSO_PARTNER_KEY"]},
)
with urllib.request.urlopen(request) as response:
    payload = json.load(response)
for item in payload["data"]:
    print(item["id"], item["period"], item["status"], item["amount"])
```

A `draft` includes `boxes`, `amount` and `version`. An `is_estimate` of `true` means the tax service is not active: there are no boxes and no approval page. The [AEAT import file](https://docs.melso.ai/api/reference/tax-returns/get-tax-return-aeat-file.md) is preparation, not a filing.

## Approval

**Live**, and a person decides. This returns the signed-in page where the owner authorizes one exact version. Sending the link does not approve anything. A partner key that calls `POST .../approve` receives `403` `person_required`. Attesting an approval from your own interface is **planned**.

```bash theme={null}
curl --fail-with-body -X POST \
  "$MELSO_API/companies/$COMPANY_ID/tax-returns/$TAX_RETURN_ID/approval-link" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY"
```

```javascript theme={null}
const response = await fetch(
  `${process.env.MELSO_API}/companies/${process.env.COMPANY_ID}/tax-returns/${process.env.TAX_RETURN_ID}/approval-link`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.MELSO_PARTNER_KEY}` },
  },
);
if (!response.ok) throw new Error(await response.text());
const link = await response.json();
console.log(link.approval_url, link.version);
```

Share `approval_url` with the owner. A changed draft needs a new link and a new decision. Authorizing opens the case in which professionals review and file that version.

## Filing receipt

**Live** to read the return. **Manual-assisted** to file it. There is no partner call that submits the 303 to the AEAT. After professionals file, read the return again. `status` `filed` and `filed_at` are the live record. `filing.reference` is the número de justificante, `filing.csv` is the código seguro de verificación, and `filing.receipt` is the PDF. The OpenAPI schema marks `filing` as preview, so that object's fields can still change.

```bash theme={null}
curl --fail-with-body \
  "$MELSO_API/companies/$COMPANY_ID/tax-returns/$TAX_RETURN_ID" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY"
```

```javascript theme={null}
const response = await fetch(
  `${process.env.MELSO_API}/companies/${process.env.COMPANY_ID}/tax-returns/${process.env.TAX_RETURN_ID}`,
  { headers: { Authorization: `Bearer ${process.env.MELSO_PARTNER_KEY}` } },
);
if (!response.ok) throw new Error(await response.text());
const taxReturn = await response.json();
console.log(taxReturn.status, taxReturn.filed_at, taxReturn.filing);
```

`POST .../mark-filed` records that the company filed the return itself. It is not how Melso's filing is proved. The webhook `tax_return.filed` is **planned**.

## Affiliation on Sistema RED

**Planned** and **manual-assisted**. This asks Melso to send an employee's alta, baja or variación to the TGSS. It is not callable yet. It is not the autónomo's own RETA registration: that live path is the case below.

```bash theme={null}
curl --fail-with-body "$MELSO_API/companies/$COMPANY_ID/social-security/affiliations" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6c1a2d73-4e8f-4b90-8c2d-3e4f5a6b7c8d" \
  --data-binary @- <<'JSON'
{
  "employee_id": "6a1f0c2e-4b3d-4e5f-8a9b-0c1d2e3f4a5b",
  "action": "alta",
  "effective_date": "2026-11-02"
}
JSON
```

```python theme={null}
import json, os, urllib.request
body = json.dumps({
    "employee_id": "6a1f0c2e-4b3d-4e5f-8a9b-0c1d2e3f4a5b",
    "action": "alta",
    "effective_date": "2026-11-02",
}).encode()
request = urllib.request.Request(
    os.environ["MELSO_API"] + "/companies/" + os.environ["COMPANY_ID"] + "/social-security/affiliations",
    data=body,
    headers={
        "Authorization": "Bearer " + os.environ["MELSO_PARTNER_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "6c1a2d73-4e8f-4b90-8c2d-3e4f5a6b7c8d",
    },
    method="POST",
)
```

The employee needs `tax_id`, `social_security_number` and `employment` filled. An alta has to reach the TGSS before the employee starts. The snippet stops before sending: the operation is not callable yet.

## Invoice and Verifactu

**Live.** Saving the draft is private beta on REST. Issuing, which registers the invoice with VERI\*FACTU or TicketBAI, is a person's decision in the app. A partner key does not issue.

```bash theme={null}
curl --fail-with-body "$MELSO_API/companies/$COMPANY_ID/invoices" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7d2b3e84-5f90-4ca1-9d3e-4f5a6b7c8d9e" \
  --data-binary @- <<'JSON'
{
  "customer_id": "3f6c2a1e-8b7d-4c5e-9f10-2a3b4c5d6e7f",
  "issue_date": "2026-10-15",
  "due_date": "2026-11-14",
  "irpf_rate": "15",
  "lines": [
    {
      "description": "Diseño de identidad visual",
      "quantity": "1",
      "unit_price": "1200.00",
      "vat_rate": "21",
      "product_kind": "services"
    }
  ]
}
JSON
```

```javascript theme={null}
const response = await fetch(`${process.env.MELSO_API}/companies/${process.env.COMPANY_ID}/invoices`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MELSO_PARTNER_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "7d2b3e84-5f90-4ca1-9d3e-4f5a6b7c8d9e",
  },
  body: JSON.stringify({
    customer_id: "3f6c2a1e-8b7d-4c5e-9f10-2a3b4c5d6e7f",
    issue_date: "2026-10-15",
    due_date: "2026-11-14",
    irpf_rate: "15",
    lines: [{
      description: "Diseño de identidad visual",
      quantity: "1",
      unit_price: "1200.00",
      vat_rate: "21",
      product_kind: "services",
    }],
  }),
});
if (!response.ok) throw new Error(await response.text());
const draft = await response.json();
const link = await fetch(
  `${process.env.MELSO_API}/companies/${process.env.COMPANY_ID}/invoices/${draft.id}/approval-link`,
  { method: "POST", headers: { Authorization: `Bearer ${process.env.MELSO_PARTNER_KEY}` } },
);
if (!link.ok) throw new Error(await link.text());
console.log((await link.json()).approval_url);
```

The owner opens `approval_url` and issues that version. Issuing registers it with VERI\*FACTU in the common territory and the Canary Islands, or with TicketBAI in Álava, Bizkaia and Gipuzkoa. The legal PDF exists once `pdf_ready` is true.

## Alta de autónomo y constituir una SL

**Live** and **manual-assisted**. Read the kind's `details_schema` before sending `details`. `constitucion_sl` is paid by the owner before Melso starts when that kind is a one-off service, and the response then carries `payment_url`. `submit: true` sends a covered case to professionals.

```bash theme={null}
curl --fail-with-body "$MELSO_API/companies/$COMPANY_ID/case-kinds" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY"
```

```bash theme={null}
curl --fail-with-body "$MELSO_API/companies/$COMPANY_ID/cases" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8e3c4f95-6091-4db2-ae4f-5a6b7c8d9e0f" \
  --data-binary @- <<'JSON'
{
  "kind": "alta",
  "details": {"start_date": "2026-12-01", "activity": "Diseño gráfico para empresas"},
  "note": "Empieza a facturar el 1 de diciembre.",
  "submit": true
}
JSON
```

```bash theme={null}
curl --fail-with-body "$MELSO_API/companies/$COMPANY_ID/cases" \
  -H "Authorization: Bearer $MELSO_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f4d5096-7102-4ec3-bf50-6b7c8d9e0f10" \
  --data-binary @- <<'JSON'
{
  "kind": "constitucion_sl",
  "note": "Sociedad limitada para la actividad de diseño.",
  "submit": false
}
JSON
```

```javascript theme={null}
const headers = {
  Authorization: `Bearer ${process.env.MELSO_PARTNER_KEY}`,
  "Content-Type": "application/json",
  "Idempotency-Key": crypto.randomUUID(),
};
const response = await fetch(`${process.env.MELSO_API}/companies/${process.env.COMPANY_ID}/cases`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    kind: "constitucion_sl",
    note: "Sociedad limitada para la actividad de diseño.",
    submit: false,
  }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

The same cases are available to a signed-in person through MCP `gestiones`. Professionals do the registration and the incorporation. The case is done when its status is `done` and it carries the administration's `external_reference`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.