i18n - docs translations (#17433)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
committed by
GitHub
parent
c8c821a692
commit
2353bc62cc
@@ -1,147 +1,147 @@
|
||||
---
|
||||
title: APIs
|
||||
description: Query and modify your CRM data programmatically using REST or GraphQL.
|
||||
description: Consulta y modifica tus datos de CRM de forma programática usando REST o GraphQL.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
Twenty was built to be developer-friendly, offering powerful APIs that adapt to your custom data model. We provide four distinct API types to meet different integration needs.
|
||||
Twenty fue creado para ser amigable con los desarrolladores, ofreciendo APIs potentes que se adaptan a tu modelo de datos personalizado. Proveemos cuatro tipos de API distintos para satisfacer diferentes necesidades de integración.
|
||||
|
||||
## Developer-First Approach
|
||||
## Enfoque centrado en el desarrollador
|
||||
|
||||
Twenty generates APIs specifically for your data model:
|
||||
Twenty genera APIs específicamente para tu modelo de datos:
|
||||
|
||||
* **No long IDs required**: Use your object and field names directly in endpoints
|
||||
* **Standard and custom objects treated equally**: Your custom objects get the same API treatment as built-in ones
|
||||
* **Dedicated endpoints**: Each object and field gets its own API endpoint
|
||||
* **Custom documentation**: Generated specifically for your workspace's data model
|
||||
* **No se requieren IDs largos**: Usa los nombres de tus objetos y campos directamente en los endpoints.
|
||||
* **Objetos estándar y personalizados tratados por igual**: Tus objetos personalizados reciben el mismo tratamiento de API que los incorporados.
|
||||
* **Endpoints dedicados**: Cada objeto y campo recibe su propio endpoint de API.
|
||||
* **Documentación personalizada**: Generada específicamente para el modelo de datos de tu espacio de trabajo.
|
||||
|
||||
<Note>
|
||||
Your personalized API documentation is available under **Settings → API & Webhooks** after creating an API key. Since Twenty generates APIs that match your custom data model, the documentation is unique to your workspace.
|
||||
Tu documentación personalizada de la API está disponible en **Configuración → API & Webhooks** después de crear una clave de API. Como Twenty genera APIs que coinciden con tu modelo de datos personalizado, la documentación es única para tu espacio de trabajo.
|
||||
</Note>
|
||||
|
||||
## The Two API Types
|
||||
## Los dos tipos de API
|
||||
|
||||
### Core API
|
||||
### API Principal
|
||||
|
||||
Accessed on `/rest/` or `/graphql/`
|
||||
Accesible en `/rest/` o `/graphql/`
|
||||
|
||||
Work with your actual **records** (the data):
|
||||
Trabaja con tus **registros** reales (los datos):
|
||||
|
||||
* Create, read, update, delete People, Companies, Opportunities, etc.
|
||||
* Query and filter data
|
||||
* Manage record relationships
|
||||
* Crear, leer, actualizar y eliminar Personas, Empresas, Oportunidades, etc.
|
||||
* Consultar y filtrar datos
|
||||
* Gestionar relaciones de registros
|
||||
|
||||
### Metadata API
|
||||
### API de Metadatos
|
||||
|
||||
Accessed on `/rest/metadata/` or `/metadata/`
|
||||
Accesible en `/rest/metadata/` o `/metadata/`
|
||||
|
||||
Manage your **workspace and data model**:
|
||||
Administra tu **espacio de trabajo y modelo de datos**:
|
||||
|
||||
* Create, modify, or delete objects and fields
|
||||
* Configure workspace settings
|
||||
* Define relationships between objects
|
||||
* Crear, modificar o eliminar objetos y campos
|
||||
* Configurar ajustes del espacio de trabajo
|
||||
* Define relaciones entre objetos
|
||||
|
||||
## REST vs GraphQL
|
||||
|
||||
Both Core and Metadata APIs are available in REST and GraphQL formats:
|
||||
Tanto las API Core como las de Metadatos están disponibles en formatos REST y GraphQL:
|
||||
|
||||
| Format | Available Operations |
|
||||
| ----------- | ---------------------------------------------------------- |
|
||||
| **REST** | CRUD, batch operations, upserts |
|
||||
| **GraphQL** | Same + **batch upserts**, relationship queries in one call |
|
||||
| Formato | Operaciones disponibles |
|
||||
| ----------- | ----------------------------------------------------------------------------- |
|
||||
| **REST** | CRUD, operaciones por lotes, upserts |
|
||||
| **GraphQL** | Lo mismo + **upserts por lotes**, consultas de relaciones en una sola llamada |
|
||||
|
||||
Choose based on your needs — both formats access the same data.
|
||||
Elige según tus necesidades — ambos formatos acceden a los mismos datos.
|
||||
|
||||
## API Endpoints
|
||||
## Puntos de Acceso de API
|
||||
|
||||
| Environment | Base URL |
|
||||
| --------------- | ------------------------- |
|
||||
| **Cloud** | `https://api.twenty.com/` |
|
||||
| **Self-Hosted** | `https://{your-domain}/` |
|
||||
| Entorno | URL base |
|
||||
| ------------------- | ------------------------- |
|
||||
| **Nube** | `https://api.twenty.com/` |
|
||||
| **Autoalojamiento** | `https://{your-domain}/` |
|
||||
|
||||
## Authentication
|
||||
## Autenticación
|
||||
|
||||
Every API request requires an API key in the header:
|
||||
Cada solicitud a la API requiere una clave de API en el encabezado:
|
||||
|
||||
```
|
||||
Authorization: Bearer YOUR_API_KEY
|
||||
```
|
||||
|
||||
### Create an API Key
|
||||
### Crear una Clave de API
|
||||
|
||||
1. Go to **Settings → APIs & Webhooks**
|
||||
2. Click **+ Create key**
|
||||
3. Configure:
|
||||
* **Name**: Descriptive name for the key
|
||||
* **Expiration Date**: When the key expires
|
||||
4. Click **Save**
|
||||
5. **Copy immediately** — the key is only shown once
|
||||
1. Ve a **Configuración → APIs y Webhooks**
|
||||
2. Haz clic en **+ Crear clave**
|
||||
3. Configurar:
|
||||
* **Nombre**: Nombre descriptivo para la clave
|
||||
* **Fecha de vencimiento**: Cuándo expira la clave
|
||||
4. Haga clic en **Guardar**
|
||||
5. **Copia de inmediato** — la clave solo se muestra una vez
|
||||
|
||||
<VimeoEmbed videoId="928786722" title="Creating API key" />
|
||||
<VimeoEmbed videoId="928786722" title="Creación de clave de API" />
|
||||
|
||||
<Warning>
|
||||
Your API key grants access to sensitive data. Don't share it with untrusted services. If compromised, disable it immediately and generate a new one.
|
||||
Tu clave de API concede acceso a datos sensibles. No la compartas con servicios no confiables. Si se ve comprometida, desactívala de inmediato y genera una nueva.
|
||||
</Warning>
|
||||
|
||||
### Assign a Role to an API Key
|
||||
### Asignar un rol a una clave de API
|
||||
|
||||
For better security, assign a specific role to limit access:
|
||||
Para mayor seguridad, asigna un rol específico para limitar el acceso:
|
||||
|
||||
1. Go to **Settings → Roles**
|
||||
2. Click on the role to assign
|
||||
3. Open the **Assignment** tab
|
||||
4. Under **API Keys**, click **+ Assign to API key**
|
||||
5. Select the API key
|
||||
1. Ve a **Configuración → Roles**
|
||||
2. Haz clic en el rol que deseas asignar
|
||||
3. Abre la pestaña **Asignación**
|
||||
4. En **Claves de API**, haz clic en **+ Asignar a clave de API**
|
||||
5. Selecciona la clave de API
|
||||
|
||||
The key will inherit that role's permissions. See [Permissions](/l/es/user-guide/permissions-access/capabilities/permissions) for details.
|
||||
La clave heredará los permisos de ese rol. Consulta [Permisos](/l/es/user-guide/permissions-access/capabilities/permissions) para más detalles.
|
||||
|
||||
### Manage API Keys
|
||||
### Gestionar Claves de API
|
||||
|
||||
**Regenerate**: Settings → APIs & Webhooks → Click key → **Regenerate**
|
||||
**Regenerar**: Configuración → APIs & Webhooks → Haz clic en la clave → **Regenerar**
|
||||
|
||||
**Delete**: Settings → APIs & Webhooks → Click key → **Delete**
|
||||
**Eliminar**: Configuración → APIs & Webhooks → Haz clic en la clave → **Eliminar**
|
||||
|
||||
## API Playground
|
||||
## Playground de la API
|
||||
|
||||
Test your APIs directly in the browser with our built-in playground — available for both **REST** and **GraphQL**.
|
||||
Prueba tus API directamente en el navegador con nuestro playground integrado — disponible tanto para **REST** como para **GraphQL**.
|
||||
|
||||
### Access the Playground
|
||||
### Accede al Playground
|
||||
|
||||
1. Go to **Settings → APIs & Webhooks**
|
||||
2. Create an API key (required)
|
||||
3. Click on **REST API** or **GraphQL API** to open the playground
|
||||
1. Ve a **Configuración → APIs y Webhooks**
|
||||
2. Crea una clave de API (obligatorio)
|
||||
3. Haz clic en **REST API** o **GraphQL API** para abrir el playground
|
||||
|
||||
### What You Get
|
||||
### Lo que obtienes
|
||||
|
||||
* **Interactive documentation**: Generated for your specific data model
|
||||
* **Live testing**: Execute real API calls against your workspace
|
||||
* **Schema explorer**: Browse available objects, fields, and relationships
|
||||
* **Request builder**: Construct queries with autocomplete
|
||||
* **Documentación interactiva**: Generada para tu modelo de datos específico
|
||||
* **Pruebas en vivo**: Ejecuta llamadas reales a la API en tu espacio de trabajo
|
||||
* **Explorador de esquemas**: Navega por los objetos, campos y relaciones disponibles
|
||||
* **Constructor de solicitudes**: Crea consultas con autocompletado
|
||||
|
||||
The playground reflects your custom objects and fields, so documentation is always accurate for your workspace.
|
||||
El playground refleja tus objetos y campos personalizados, por lo que la documentación siempre es precisa para tu espacio de trabajo.
|
||||
|
||||
## Batch Operations
|
||||
## Operaciones por Lotes
|
||||
|
||||
Both REST and GraphQL support batch operations:
|
||||
Tanto REST como GraphQL admiten operaciones por lotes:
|
||||
|
||||
* **Batch size**: Up to 60 records per request
|
||||
* **Operations**: Create, update, delete multiple records
|
||||
* **Tamaño del lote**: Hasta 60 registros por solicitud
|
||||
* **Operaciones**: Crear, actualizar y eliminar múltiples registros
|
||||
|
||||
**GraphQL-only features:**
|
||||
**Características exclusivas de GraphQL:**
|
||||
|
||||
* **Batch Upsert**: Create or update in one call
|
||||
* Use plural object names (e.g., `CreateCompanies` instead of `CreateCompany`)
|
||||
* **Upsert por lotes**: Crear o actualizar en una llamada
|
||||
* Usa nombres de objetos en plural (por ejemplo, `CreateCompanies` en lugar de `CreateCompany`)
|
||||
|
||||
## Rate Limits
|
||||
## Límites de tasa
|
||||
|
||||
API requests are throttled to ensure platform stability:
|
||||
Las solicitudes a la API se limitan para garantizar la estabilidad de la plataforma:
|
||||
|
||||
| Limit | Value |
|
||||
| -------------- | -------------------- |
|
||||
| **Requests** | 100 calls per minute |
|
||||
| **Batch size** | 60 records per call |
|
||||
| Límite | Valor |
|
||||
| ------------------- | -------------------------- |
|
||||
| **Solicitudes** | 100 solicitudes por minuto |
|
||||
| **Tamaño del lote** | 60 registros por llamada |
|
||||
|
||||
<Tip>
|
||||
Use batch operations to maximize throughput — process up to 60 records in a single API call instead of making individual requests.
|
||||
Usa operaciones por lotes para maximizar el rendimiento — procesa hasta 60 registros en una sola llamada a la API en lugar de hacer solicitudes individuales.
|
||||
</Tip>
|
||||
|
||||
@@ -1,81 +1,88 @@
|
||||
---
|
||||
title: Twenty Apps
|
||||
description: Build and manage Twenty customizations as code.
|
||||
title: Aplicaciones de Twenty
|
||||
description: Crea y gestiona personalizaciones de Twenty como código.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Apps are currently in alpha testing. The feature is functional but still evolving.
|
||||
Las aplicaciones están actualmente en pruebas alfa. La funcionalidad es operativa, pero sigue evolucionando.
|
||||
</Warning>
|
||||
|
||||
## What Are Apps?
|
||||
## ¿Qué son las aplicaciones?
|
||||
|
||||
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and serverless functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
|
||||
Las aplicaciones te permiten crear y administrar personalizaciones de Twenty **como código**. En lugar de configurar todo a través de la interfaz de usuario, defines tu modelo de datos y funciones sin servidor en código, lo que hace más rápido crear, mantener y desplegar en múltiples espacios de trabajo.
|
||||
|
||||
**What you can do today:**
|
||||
**Lo que puedes hacer hoy:**
|
||||
|
||||
* Define custom objects and fields as code (managed data model)
|
||||
* Build serverless functions with custom triggers
|
||||
* Deploy the same app across multiple workspaces
|
||||
* Define objetos y campos personalizados como código (modelo de datos gestionado)
|
||||
* Crea funciones sin servidor con desencadenadores personalizados
|
||||
* Despliega la misma aplicación en múltiples espacios de trabajo
|
||||
|
||||
**Coming soon:**
|
||||
**Próximamente:**
|
||||
|
||||
* Custom UI layouts and components
|
||||
* Diseños y componentes de la interfaz de usuario personalizados
|
||||
|
||||
## Prerequisites
|
||||
## Prerrequisitos
|
||||
|
||||
* Node.js 24+ and Yarn 4
|
||||
* A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
|
||||
* Node.js 24+ y Yarn 4
|
||||
* Un espacio de trabajo de Twenty y una clave de API (créala en https://app.twenty.com/settings/api-webhooks)
|
||||
|
||||
## Getting Started
|
||||
## Primeros pasos
|
||||
|
||||
Create a new app using the official scaffolder, then authenticate and start developing:
|
||||
Crea una aplicación nueva usando el generador oficial, luego autentícate y comienza a desarrollar:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app
|
||||
# Crear la estructura de una nueva aplicación
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
|
||||
# Authenticate using your API key (you'll be prompted)
|
||||
yarn auth
|
||||
# Si no usas yarn@4
|
||||
corepack enable
|
||||
yarn install
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
yarn dev
|
||||
# Autentícate con tu clave de API (se te pedirá)
|
||||
yarn auth:login
|
||||
|
||||
# Inicia el modo de desarrollo: sincroniza automáticamente los cambios locales con tu espacio de trabajo
|
||||
yarn app:dev
|
||||
```
|
||||
|
||||
From here you can:
|
||||
Desde aquí usted puede:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn create-entity
|
||||
yarn app:create-entity
|
||||
|
||||
# Generate a typed Twenty client and workspace entity types
|
||||
yarn generate
|
||||
yarn app:generate
|
||||
|
||||
# Run a one‑time sync (instead of watch mode)
|
||||
yarn sync
|
||||
yarn app:sync
|
||||
|
||||
# Watch your application's functions logs
|
||||
yarn logs
|
||||
yarn function:logs
|
||||
|
||||
# Execute a function by name
|
||||
yarn function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn uninstall
|
||||
yarn app:uninstall
|
||||
|
||||
# Display commands' help
|
||||
yarn help
|
||||
yarn app:help
|
||||
```
|
||||
|
||||
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
Consulta también: las páginas de referencia de la CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) y [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
|
||||
## Project structure (scaffolded)
|
||||
## Estructura del proyecto (generada)
|
||||
|
||||
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
|
||||
Cuando ejecutas `npx create-twenty-app@latest my-twenty-app`, el generador:
|
||||
|
||||
* Copies a minimal base application into `my-twenty-app/`
|
||||
* Adds a local `twenty-sdk` dependency and Yarn 4 configuration
|
||||
* Creates config files and scripts wired to the `twenty` CLI
|
||||
* Generates a default application config and a default function role
|
||||
* Copia una aplicación base mínima en `my-twenty-app/`
|
||||
* Añade una dependencia local de `twenty-sdk` y la configuración de Yarn 4
|
||||
* Crea archivos de configuración y scripts vinculados a la CLI `twenty`
|
||||
* Genera una configuración de aplicación predeterminada y un rol de función predeterminado
|
||||
|
||||
A freshly scaffolded app looks like this:
|
||||
Una aplicación recién generada se ve así:
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -85,79 +92,144 @@ my-twenty-app/
|
||||
.nvmrc
|
||||
.yarnrc.yml
|
||||
.yarn/
|
||||
releases/
|
||||
yarn-4.9.2.cjs
|
||||
install-state.gz
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
src/
|
||||
application.config.ts
|
||||
role.config.ts
|
||||
// your entities, actions, and other app files
|
||||
app/
|
||||
application.config.ts # Obligatorio - configuración principal de la aplicación
|
||||
default-function.role.ts # Rol predeterminado para las funciones sin servidor
|
||||
// tus entidades (*.object.ts, *.function.ts, *.role.ts)
|
||||
utils/ # Opcional - implementaciones de manejadores y utilidades
|
||||
```
|
||||
|
||||
At a high level:
|
||||
### Convención sobre configuración
|
||||
|
||||
* **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall`, and `auth` that delegate to the local `twenty` CLI.
|
||||
* **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
|
||||
* **.nvmrc**: Pins the Node.js version expected by the project.
|
||||
* **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your app’s TypeScript sources.
|
||||
* **README.md**: A short README in the app root with basic instructions.
|
||||
* **src/**: The main place where you define your application-as-code:
|
||||
* `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See “Application config” below.
|
||||
* `role.config.ts`: Default function role used by your serverless functions. See “Default function role” below.
|
||||
* Future entities, actions/functions, and any supporting code you add.
|
||||
Las aplicaciones usan un enfoque de **convención sobre configuración** en el que las entidades se detectan por su sufijo de archivo. Esto permite una organización flexible dentro de la carpeta `src/app/`:
|
||||
|
||||
Later commands will add more files and folders:
|
||||
| Sufijo de archivo | Tipo de entidad |
|
||||
| ----------------- | -------------------------------------- |
|
||||
| `*.object.ts` | Definiciones de objetos personalizados |
|
||||
| `*.function.ts` | Definiciones de funciones sin servidor |
|
||||
| `*.role.ts` | Definiciones de roles |
|
||||
|
||||
* `yarn generate` will create a `generated/` folder (typed Twenty client + workspace types).
|
||||
* `yarn create-entity` will add entity definition files under `src/` for your custom objects.
|
||||
### Organizaciones de carpetas compatibles
|
||||
|
||||
## Authentication
|
||||
Puedes organizar tus entidades con cualquiera de estos patrones:
|
||||
|
||||
The first time you run `yarn auth`, you'll be prompted for:
|
||||
**Tradicional (por tipo):**
|
||||
|
||||
* API URL (defaults to http://localhost:3000 or your current workspace profile)
|
||||
* API key
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── objects/
|
||||
│ └── postCard.object.ts
|
||||
├── functions/
|
||||
│ └── createPostCard.function.ts
|
||||
└── roles/
|
||||
└── admin.role.ts
|
||||
```
|
||||
|
||||
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch using `--workspace <name>`.
|
||||
**Basado en funcionalidades:**
|
||||
|
||||
Examples:
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
└── post-card/
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── postCardAdmin.role.ts
|
||||
```
|
||||
|
||||
**Plano:**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── admin.role.ts
|
||||
```
|
||||
|
||||
A grandes rasgos:
|
||||
|
||||
* **package.json**: Declara el nombre de la aplicación, la versión, los entornos (Node 24+, Yarn 4) y agrega `twenty-sdk` además de scripts como `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall` y `auth` que delegan en la CLI local `twenty`.
|
||||
* **.gitignore**: Ignora artefactos comunes como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, carpetas de cobertura, archivos de registro y archivos `.env*`.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloquean y configuran la cadena de herramientas Yarn 4 utilizada por el proyecto.
|
||||
* **.nvmrc**: Fija la versión de Node.js esperada por el proyecto.
|
||||
* **eslint.config.mjs** y **tsconfig.json**: Proporcionan linting y configuración de TypeScript para las fuentes de TypeScript de tu aplicación.
|
||||
* **README.md**: Un README breve en la raíz de la aplicación con instrucciones básicas.
|
||||
* **src/app/**: El lugar principal donde defines tu aplicación como código:
|
||||
* `application.config.ts`: Configuración global de tu aplicación (metadatos y vinculación en tiempo de ejecución). Consulta "Configuración de la aplicación" más abajo.
|
||||
* `*.role.ts`: Definiciones de roles usadas por tus funciones sin servidor. Consulta "Rol de función predeterminado" más abajo.
|
||||
* `*.object.ts`: Definiciones de objetos personalizados.
|
||||
* `*.function.ts`: Definiciones de funciones sin servidor.
|
||||
* **src/utils/**: Carpeta opcional para implementaciones de controladores y utilidades.
|
||||
|
||||
Comandos posteriores añadirán más archivos y carpetas:
|
||||
|
||||
* `yarn app:generate` creará una carpeta `generated/` (cliente tipado de Twenty + tipos del espacio de trabajo).
|
||||
* `yarn app:create-entity` añadirá archivos de definición de entidades en `src/app/` para tus objetos, funciones o roles personalizados.
|
||||
l
|
||||
|
||||
## Autenticación
|
||||
|
||||
La primera vez que ejecutes `yarn auth:login`, se te solicitará:
|
||||
|
||||
* URL de la API (por defecto http://localhost:3000 o el perfil de tu espacio de trabajo actual)
|
||||
* Clave de API
|
||||
|
||||
Tus credenciales se almacenan por usuario en `~/.twenty/config.json`. Puedes mantener varios perfiles y cambiar entre ellos.
|
||||
|
||||
### Gestión de espacios de trabajo
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn auth
|
||||
yarn auth:login
|
||||
|
||||
# Use a specific workspace profile
|
||||
yarn auth --workspace my-custom-workspace
|
||||
# Login to a specific workspace profile
|
||||
yarn auth:login --workspace my-custom-workspace
|
||||
|
||||
# List all configured workspaces
|
||||
yarn auth:list
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn auth:switch
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn auth:status
|
||||
```
|
||||
|
||||
## Use the SDK resources (types & config)
|
||||
Una vez que hayas cambiado de espacio de trabajo con `auth:switch`, todos los comandos posteriores usarán ese espacio de trabajo de forma predeterminada. Aún puedes anularlo temporalmente con `--workspace <name>`.
|
||||
|
||||
The twenty-sdk provides typed building blocks you use inside your app. Below are the key pieces you'll touch most often.
|
||||
## Usa los recursos del SDK (tipos y configuración)
|
||||
|
||||
### Defining objects
|
||||
El twenty-sdk proporciona bloques de construcción tipados y funciones auxiliares que utilizas dentro de tu aplicación. A continuación, las partes clave que usarás con más frecuencia.
|
||||
|
||||
Custom objects are regular TypeScript classes annotated with decorators from `twenty-sdk`. They live under `src/objects/` in your app and describe both schema and behavior for records in your workspace.
|
||||
### Funciones auxiliares
|
||||
|
||||
Here is an example `postCard` object from the Hello World app:
|
||||
El SDK proporciona cuatro funciones auxiliares con validación incorporada para definir las entidades de tu aplicación:
|
||||
|
||||
| Función | Propósito |
|
||||
| ------------------ | ----------------------------------------------- |
|
||||
| `defineApp()` | Configura los metadatos de la aplicación |
|
||||
| `defineObject()` | Define objetos personalizados con campos |
|
||||
| `defineFunction()` | Define funciones sin servidor con controladores |
|
||||
| `defineRole()` | Configura permisos de roles y acceso a objetos |
|
||||
|
||||
Estas funciones validan tu configuración en tiempo de ejecución y proporcionan un mejor autocompletado en el IDE y seguridad de tipos.
|
||||
|
||||
### Definir objetos
|
||||
|
||||
Los objetos personalizados describen tanto el esquema como el comportamiento de los registros en tu espacio de trabajo. Usa `defineObject()` para definir objetos con validación incorporada:
|
||||
|
||||
```typescript
|
||||
import { type Note } from '../../generated';
|
||||
|
||||
import {
|
||||
type AddressField,
|
||||
Field,
|
||||
FieldType,
|
||||
type FullNameField,
|
||||
Object,
|
||||
OnDeleteAction,
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
// src/app/postCard.object.ts
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
enum PostCardStatus {
|
||||
DRAFT = 'DRAFT',
|
||||
@@ -166,176 +238,186 @@ enum PostCardStatus {
|
||||
RETURNED = 'RETURNED',
|
||||
}
|
||||
|
||||
@Object({
|
||||
export default defineObject({
|
||||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||||
nameSingular: 'postCard',
|
||||
namePlural: 'postCards',
|
||||
labelSingular: 'Post card',
|
||||
labelPlural: 'Post cards',
|
||||
description: ' A post card object',
|
||||
labelSingular: 'Post Card',
|
||||
labelPlural: 'Post Cards',
|
||||
description: 'A post card object',
|
||||
icon: 'IconMail',
|
||||
})
|
||||
export class PostCard {
|
||||
@Field({
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
content: string;
|
||||
|
||||
@Field({
|
||||
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
|
||||
type: FieldType.FULL_NAME,
|
||||
label: 'Recipient name',
|
||||
icon: 'IconUser',
|
||||
})
|
||||
recipientName: FullNameField;
|
||||
|
||||
@Field({
|
||||
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
|
||||
type: FieldType.ADDRESS,
|
||||
label: 'Recipient address',
|
||||
icon: 'IconHome',
|
||||
})
|
||||
recipientAddress: AddressField;
|
||||
|
||||
@Field({
|
||||
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
|
||||
type: FieldType.SELECT,
|
||||
label: 'Status',
|
||||
icon: 'IconSend',
|
||||
defaultValue: `'${PostCardStatus.DRAFT}'`,
|
||||
options: [
|
||||
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
|
||||
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
|
||||
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
|
||||
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
|
||||
],
|
||||
})
|
||||
status: PostCardStatus;
|
||||
|
||||
@Relation({
|
||||
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
icon: 'IconComment',
|
||||
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
|
||||
onDelete: OnDeleteAction.CASCADE,
|
||||
})
|
||||
notes: Note[];
|
||||
|
||||
@Field({
|
||||
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
|
||||
type: FieldType.DATE_TIME,
|
||||
label: 'Delivered at',
|
||||
icon: 'IconCheck',
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
})
|
||||
deliveredAt?: Date;
|
||||
}
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
name: 'content',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
|
||||
name: 'recipientName',
|
||||
type: FieldType.FULL_NAME,
|
||||
label: 'Recipient name',
|
||||
icon: 'IconUser',
|
||||
},
|
||||
{
|
||||
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
|
||||
name: 'recipientAddress',
|
||||
type: FieldType.ADDRESS,
|
||||
label: 'Recipient address',
|
||||
icon: 'IconHome',
|
||||
},
|
||||
{
|
||||
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
|
||||
name: 'status',
|
||||
type: FieldType.SELECT,
|
||||
label: 'Status',
|
||||
icon: 'IconSend',
|
||||
defaultValue: `'${PostCardStatus.DRAFT}'`,
|
||||
options: [
|
||||
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
|
||||
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
|
||||
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
|
||||
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
|
||||
],
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
|
||||
name: 'deliveredAt',
|
||||
type: FieldType.DATE_TIME,
|
||||
label: 'Delivered at',
|
||||
icon: 'IconCheck',
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Key points:
|
||||
Puntos clave:
|
||||
|
||||
* The `@Object` decorator defines the object identity and labels used across the workspace; its `universalIdentifier` must be unique and stable across deployments.
|
||||
* Each `@Field` decorator defines a field on the object with a type, label, and its own stable `universalIdentifier`.
|
||||
* `@Relation` wires this object to other objects (standard or custom) and controls cascade behavior with `onDelete`.
|
||||
* You can scaffold new objects using `yarn create-entity`, which guides you through naming, fields, and relationships, then generates object files similar to the `postCard` example.
|
||||
* Usa `defineObject()` para validación incorporada y mejor soporte del IDE.
|
||||
* El `universalIdentifier` debe ser único y estable entre implementaciones.
|
||||
* Cada campo requiere `name`, `type`, `label` y su propio `universalIdentifier` estable.
|
||||
* La matriz `fields` es opcional: puedes definir objetos sin campos personalizados.
|
||||
* Puedes generar nuevos objetos usando `yarn app:create-entity`, que te guía por el nombrado, los campos y las relaciones.
|
||||
|
||||
### Application config (application.config.ts)
|
||||
<Note>
|
||||
**Los campos base se crean automáticamente.** Cuando defines un objeto personalizado, Twenty añade automáticamente campos estándar como `name`, `createdAt`, `updatedAt`, `createdBy`, `position` y `deletedAt`. No necesitas definir estos en tu matriz `fields` — solo agrega tus campos personalizados.
|
||||
</Note>
|
||||
|
||||
Every app has a single `application.config.ts` file that describes:
|
||||
<Accordion title="Alternativa: sintaxis basada en decoradores">
|
||||
También puedes definir objetos usando decoradores de TypeScript. Este enfoque usa sintaxis basada en clases con los decoradores `@Object`, `@Field` y `@Relation`:
|
||||
|
||||
* **Who the app is**: identifiers, display name, and description.
|
||||
* **How its functions run**: which role they use for permissions.
|
||||
* **(Optional) variables**: key–value pairs exposed to your functions as environment variables.
|
||||
```typescript
|
||||
import {
|
||||
type AddressField,
|
||||
Field,
|
||||
FieldType,
|
||||
type FullNameField,
|
||||
Object,
|
||||
OnDeleteAction,
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
import { type Note } from '../../generated';
|
||||
|
||||
When you scaffold a new app, you start with a minimal config:
|
||||
@Object({
|
||||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||||
nameSingular: 'postCard',
|
||||
namePlural: 'postCards',
|
||||
labelSingular: 'Post card',
|
||||
labelPlural: 'Post cards',
|
||||
description: 'A post card object',
|
||||
icon: 'IconMail',
|
||||
})
|
||||
export class PostCard {
|
||||
@Field({
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
content: string;
|
||||
|
||||
@Relation({
|
||||
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
icon: 'IconComment',
|
||||
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
|
||||
onDelete: OnDeleteAction.CASCADE,
|
||||
})
|
||||
notes: Note[];
|
||||
}
|
||||
```
|
||||
|
||||
Nota: El enfoque de decoradores requiere `experimentalDecorators` en tu configuración de TypeScript.
|
||||
</Accordion>
|
||||
|
||||
### Configuración de la aplicación (application.config.ts)
|
||||
|
||||
Cada aplicación tiene un único archivo `application.config.ts` que describe:
|
||||
|
||||
* **Qué es la aplicación**: identificadores, nombre para mostrar y descripción.
|
||||
* **Cómo se ejecutan sus funciones**: qué rol usan para permisos.
|
||||
* **Variables (opcionales)**: pares clave–valor expuestos a tus funciones como variables de entorno.
|
||||
|
||||
Usa `defineApp()` para definir la configuración de tu aplicación:
|
||||
|
||||
```typescript
|
||||
import { type ApplicationConfig } from 'twenty-sdk';
|
||||
// src/app/application.config.ts
|
||||
import { defineApp } from 'twenty-sdk';
|
||||
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '<generated-app-uuid>',
|
||||
export default defineApp({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
functionRoleUniversalIdentifier: '<generated-role-uuid>',
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
You can gradually extend this file as your app grows. For example, you can add an icon and application-scoped variables:
|
||||
|
||||
```typescript
|
||||
import { type ApplicationConfig } from 'twenty-sdk';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '<your-app-uuid>',
|
||||
displayName: 'My App',
|
||||
description: 'What your app does',
|
||||
icon: 'IconWorld', // Choose an icon by name
|
||||
icon: 'IconWorld',
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
universalIdentifier: '<uuid>',
|
||||
description: 'Default recipient used by functions',
|
||||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||||
description: 'Default recipient name for postcards',
|
||||
value: 'Jane Doe',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
functionRoleUniversalIdentifier: '<your-role-uuid>',
|
||||
};
|
||||
|
||||
export default config;
|
||||
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Notes:
|
||||
Notas:
|
||||
|
||||
* `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
|
||||
* `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `functionRoleUniversalIdentifier` must match the role you define in `role.config.ts` (see below).
|
||||
* Los campos `universalIdentifier` son ID deterministas bajo tu control; genéralos una vez y mantenlos estables entre sincronizaciones.
|
||||
* Las `applicationVariables` se convierten en variables de entorno para tus funciones (por ejemplo, `DEFAULT_RECIPIENT_NAME` está disponible como `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `functionRoleUniversalIdentifier` debe coincidir con el rol que defines en tu archivo `*.role.ts` (ver abajo).
|
||||
|
||||
#### Roles and permissions
|
||||
#### Roles y permisos
|
||||
|
||||
Applications can define roles that encapsulate permissions on your workspace’s objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app’s serverless functions.
|
||||
Las aplicaciones pueden definir roles que encapsulan permisos sobre los objetos y acciones de tu espacio de trabajo. El campo `functionRoleUniversalIdentifier` en `application.config.ts` designa el rol predeterminado que usan las funciones sin servidor de tu aplicación.
|
||||
|
||||
* The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
|
||||
* The typed client will be restricted to the permissions granted to that role.
|
||||
* Follow least‑privilege: create a dedicated role with only the permissions your functions need, then reference its universal identifier.
|
||||
* La clave de API en tiempo de ejecución inyectada como `TWENTY_API_KEY` se deriva de este rol de función predeterminado.
|
||||
* El cliente tipado estará restringido a los permisos otorgados a ese rol.
|
||||
* Sigue el principio de mínimo privilegio: crea un rol dedicado con solo los permisos que necesitan tus funciones y luego referencia su identificador universal.
|
||||
|
||||
##### Default function role (role.config.ts)
|
||||
##### Rol de función predeterminado (\*.role.ts)
|
||||
|
||||
When you scaffold a new app, the CLI also creates `src/role.config.ts`. This file exports the default role your serverless functions will use at runtime:
|
||||
Cuando generas una nueva aplicación, la CLI también crea un archivo de rol predeterminado. Usa `defineRole()` para definir roles con validación incorporada:
|
||||
|
||||
```typescript
|
||||
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
|
||||
// src/app/default-function.role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk';
|
||||
|
||||
export const functionRole: RoleConfig = {
|
||||
universalIdentifier: '<generated-role-uuid>',
|
||||
label: 'My Twenty App default function role',
|
||||
description: 'My Twenty App default function role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: false,
|
||||
};
|
||||
```
|
||||
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
The `universalIdentifier` of this role is automatically wired into `application.config.ts` as `functionRoleUniversalIdentifier`. In other words:
|
||||
|
||||
* **role.config.ts** defines what the default function role can do.
|
||||
* **application.config.ts** points to that role so your functions inherit its permissions.
|
||||
|
||||
As you move beyond the initial scaffold, you should tighten this role and make it explicit about what it can access. A more production-ready role might look closer to:
|
||||
|
||||
```typescript
|
||||
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
|
||||
|
||||
export const functionRole: RoleConfig = {
|
||||
universalIdentifier: '<your-role-uuid>',
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: false,
|
||||
@@ -363,41 +445,41 @@ export const functionRole: RoleConfig = {
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
],
|
||||
permissionFlags: ['APPLICATIONS'],
|
||||
};
|
||||
permissionFlags: [PermissionFlag.APPLICATIONS],
|
||||
});
|
||||
```
|
||||
|
||||
Notes:
|
||||
El `universalIdentifier` de este rol se referencia luego en `application.config.ts` como `functionRoleUniversalIdentifier`. En otras palabras:
|
||||
|
||||
* Start from the scaffolded role, then progressively restrict it following least‑privilege.
|
||||
* Replace the `objectPermissions` and `fieldPermissions` with the objects/fields your functions need.
|
||||
* `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
|
||||
* See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
* **\*.role.ts** define lo que puede hacer el rol de función predeterminado.
|
||||
* **application.config.ts** apunta a ese rol para que tus funciones hereden sus permisos.
|
||||
|
||||
### Serverless function config and entrypoint
|
||||
Notas:
|
||||
|
||||
Each function exports a main handler and a config describing its triggers. You can mix multiple trigger types.
|
||||
* Parte del rol generado y luego restríngele progresivamente siguiendo el principio de mínimo privilegio.
|
||||
* Reemplaza `objectPermissions` y `fieldPermissions` con los objetos/campos que necesitan tus funciones.
|
||||
* `permissionFlags` controla el acceso a capacidades a nivel de plataforma. Mantenlos al mínimo; agrega solo lo que necesites.
|
||||
* Consulta un ejemplo funcional en la aplicación Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
### Configuración y punto de entrada de funciones sin servidor
|
||||
|
||||
Cada archivo de función usa `defineFunction()` para exportar una configuración con un controlador y desencadenadores opcionales. Usa el sufijo de archivo `*.function.ts` para la detección automática.
|
||||
|
||||
```typescript
|
||||
// src/actions/create-new-post-card.ts
|
||||
import type {
|
||||
FunctionConfig,
|
||||
DatabaseEventPayload,
|
||||
ObjectRecordCreateEvent,
|
||||
CronPayload,
|
||||
} from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '../generated';
|
||||
// src/app/createPostCard.function.ts
|
||||
import { defineFunction } from 'twenty-sdk';
|
||||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '../../generated';
|
||||
|
||||
// main handler can accept parameters from route, cron, or database events
|
||||
export const main = async (
|
||||
const handler = async (
|
||||
params:
|
||||
| { name?: string }
|
||||
| RoutePayload
|
||||
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
|
||||
| CronPayload,
|
||||
) => {
|
||||
const client = new Twenty(); // generated typed client
|
||||
const name = 'name' in params
|
||||
? params.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
: 'Hello world';
|
||||
|
||||
const result = await client.mutation({
|
||||
@@ -410,14 +492,15 @@ export const main = async (
|
||||
return result;
|
||||
};
|
||||
|
||||
export const config: FunctionConfig = {
|
||||
universalIdentifier: '<function-uuid>',
|
||||
export default defineFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
handler,
|
||||
triggers: [
|
||||
// Public HTTP route trigger '/s/post-card/create'
|
||||
{
|
||||
universalIdentifier: '<route-trigger-uuid>',
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'route',
|
||||
path: '/post-card/create',
|
||||
httpMethod: 'GET',
|
||||
@@ -425,39 +508,137 @@ export const config: FunctionConfig = {
|
||||
},
|
||||
// Cron trigger (CRON pattern)
|
||||
{
|
||||
universalIdentifier: '<cron-trigger-uuid>',
|
||||
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
type: 'cron',
|
||||
pattern: '0 0 1 1 *',
|
||||
},
|
||||
// Database event trigger
|
||||
{
|
||||
universalIdentifier: '<db-trigger-uuid>',
|
||||
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
type: 'databaseEvent',
|
||||
eventName: 'person.created',
|
||||
eventName: 'person.updated',
|
||||
updatedFields: ['name'],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Tipos de desencadenadores comunes:
|
||||
|
||||
* **route**: Expone tu función en una ruta y método HTTP **bajo el endpoint `/s/`**:
|
||||
|
||||
> p. ej. `path: '/post-card/create',` -> llamar en `<APP_URL>/s/post-card/create`
|
||||
|
||||
* **cron**: Ejecuta tu función en un horario usando una expresión CRON.
|
||||
* **databaseEvent**: Se ejecuta en eventos del ciclo de vida de objetos del espacio de trabajo. Cuando la operación del evento es `updated`, se pueden especificar campos específicos que se deben escuchar en el arreglo `updatedFields`. Si se deja sin definir o vacío, cualquier actualización activará la función.
|
||||
|
||||
> p. ej., `person.updated`
|
||||
|
||||
Notas:
|
||||
|
||||
* La matriz `triggers` es opcional. Las funciones sin desencadenadores pueden usarse como funciones utilitarias llamadas por otras funciones.
|
||||
* Puedes combinar múltiples tipos de desencadenadores en una sola función.
|
||||
|
||||
### Carga útil del disparador de ruta
|
||||
|
||||
<Warning>
|
||||
**Cambio no retrocompatible (v1.16, enero de 2026):** El formato de la carga útil del disparador de ruta ha cambiado. Antes de la v1.16, los parámetros de consulta, los parámetros de ruta y el cuerpo se enviaban directamente como la carga útil. A partir de la v1.16, están anidados dentro de un objeto `RoutePayload` estructurado.
|
||||
|
||||
**Antes de la v1.16:**
|
||||
|
||||
```typescript
|
||||
const handler = async (params) => {
|
||||
const { param1, param2 } = params; // Direct access
|
||||
};
|
||||
```
|
||||
|
||||
**Después de la v1.16:**
|
||||
|
||||
```typescript
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const { param1, param2 } = event.body; // Access via .body
|
||||
const { queryParam } = event.queryStringParameters;
|
||||
const { id } = event.pathParameters;
|
||||
};
|
||||
```
|
||||
|
||||
**Para migrar las funciones existentes:** Actualiza tu controlador para desestructurar desde `event.body`, `event.queryStringParameters` o `event.pathParameters` en lugar de hacerlo directamente desde el objeto params.
|
||||
</Warning>
|
||||
|
||||
Cuando un disparador de ruta invoca tu función, esta recibe un objeto `RoutePayload` que sigue el formato de AWS HTTP API v2. Importa el tipo desde `twenty-sdk`:
|
||||
|
||||
```typescript
|
||||
import { defineFunction, type RoutePayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
// Access request data
|
||||
const { headers, queryStringParameters, pathParameters, body } = event;
|
||||
|
||||
// HTTP method and path are available in requestContext
|
||||
const { method, path } = event.requestContext.http;
|
||||
|
||||
return { message: 'Success' };
|
||||
};
|
||||
```
|
||||
|
||||
Common trigger types:
|
||||
El tipo `RoutePayload` tiene la siguiente estructura:
|
||||
|
||||
* route: Exposes your function on an HTTP path and method **under the `/s/` endpoint**:
|
||||
| Propiedad | Tipo | Descripción |
|
||||
| ---------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `headers` | `Record<string, string \| undefined>` | Encabezados HTTP (solo aquellos listados en `forwardedRequestHeaders`) |
|
||||
| `queryStringParameters` | `Record<string, string \| undefined>` | Parámetros de consulta (valores múltiples unidos con comas) |
|
||||
| `pathParameters` | `Record<string, string \| undefined>` | Parámetros de ruta extraídos del patrón de ruta (p. ej., `/users/:id` → `{ id: '123' }`) |
|
||||
| `cuerpo` | `object \| null` | Cuerpo de la solicitud analizado (JSON) |
|
||||
| `isBase64Encoded` | `booleano` | Indica si el cuerpo está codificado en base64 |
|
||||
| `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) |
|
||||
| `requestContext.http.path` | `string` | Ruta de la solicitud sin procesar |
|
||||
|
||||
> e.g. `path: '/post-card/create',` -> call on `<APP_URL>/s/post-card/create`
|
||||
### Reenvío de encabezados HTTP
|
||||
|
||||
* cron: Runs your function on a schedule using a CRON expression.
|
||||
* databaseEvent: Runs on workspace object lifecycle events
|
||||
De forma predeterminada, los encabezados HTTP de las solicitudes entrantes **no** se pasan a tu función sin servidor por razones de seguridad. Para acceder a encabezados específicos, enuméralos explícitamente en el arreglo `forwardedRequestHeaders`:
|
||||
|
||||
> e.g. `person.created`
|
||||
```typescript
|
||||
export default defineFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'webhook-handler',
|
||||
handler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'route',
|
||||
path: '/webhook',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: false,
|
||||
forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
You can create new functions in two ways:
|
||||
En tu controlador, luego puedes acceder a estos encabezados:
|
||||
|
||||
* **Scaffolded**: Run `yarn create-entity --path <custom-path>` and choose the option to add a new function. This generates a starter file under `<custom-path>` with a `main` handler and a `config` block similar to the example above.
|
||||
* **Manual**: Create a new file and export `main` and `config` yourself, following the same pattern.
|
||||
```typescript
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const signature = event.headers['x-webhook-signature'];
|
||||
const contentType = event.headers['content-type'];
|
||||
|
||||
### Generated typed client
|
||||
// Validate webhook signature...
|
||||
return { received: true };
|
||||
};
|
||||
```
|
||||
|
||||
Run yarn generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
|
||||
<Note>
|
||||
Los nombres de los encabezados se normalizan a minúsculas. Accede a ellos usando claves en minúsculas (por ejemplo, `event.headers['content-type']`).
|
||||
</Note>
|
||||
|
||||
Puedes crear funciones nuevas de dos maneras:
|
||||
|
||||
* **Generado**: Ejecuta `yarn app:create-entity` y elige la opción para añadir una nueva función. Esto genera un archivo inicial con un controlador y configuración.
|
||||
* **Manual**: Crea un nuevo archivo `*.function.ts` y usa `defineFunction()`, siguiendo el mismo patrón.
|
||||
|
||||
### Cliente tipado generado
|
||||
|
||||
Ejecuta yarn app:generate para crear un cliente tipado local en generated/ basado en el esquema de tu espacio de trabajo. Úsalo en tus funciones:
|
||||
|
||||
```typescript
|
||||
import Twenty from './generated';
|
||||
@@ -466,34 +647,34 @@ const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
```
|
||||
|
||||
The client is re-generated by `yarn generate`. Re-run after changing your objects and `yarn sync` or when onboarding to a new workspace.
|
||||
El cliente se vuelve a generar con `yarn app:generate`. Vuelve a ejecutarlo después de cambiar tus objetos y de hacer `yarn app:sync`, o al incorporarte a un nuevo espacio de trabajo.
|
||||
|
||||
#### Runtime credentials in serverless functions
|
||||
#### Credenciales en tiempo de ejecución en funciones sin servidor
|
||||
|
||||
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
|
||||
Cuando tu función se ejecuta en Twenty, la plataforma inyecta credenciales como variables de entorno antes de que tu código se ejecute:
|
||||
|
||||
* `TWENTY_API_URL`: Base URL of the Twenty API your app targets.
|
||||
* `TWENTY_API_KEY`: Short‑lived key scoped to your application’s default function role.
|
||||
* `TWENTY_API_URL`: URL base de la API de Twenty a la que apunta tu aplicación.
|
||||
* `TWENTY_API_KEY`: Clave de corta duración con alcance al rol de función predeterminado de tu aplicación.
|
||||
|
||||
Notes:
|
||||
Notas:
|
||||
|
||||
* You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
|
||||
* The API key’s permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
|
||||
* Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that role’s universal identifier.
|
||||
* No necesitas pasar la URL ni la clave de API al cliente generado. Lee `TWENTY_API_URL` y `TWENTY_API_KEY` de process.env en tiempo de ejecución.
|
||||
* Los permisos de la clave de API están determinados por el rol referenciado en tu `application.config.ts` mediante `functionRoleUniversalIdentifier`. Este es el rol predeterminado que usan las funciones sin servidor de tu aplicación.
|
||||
* Las aplicaciones pueden definir roles para seguir el principio de mínimo privilegio. Concede solo los permisos que necesitan tus funciones y después apunta `functionRoleUniversalIdentifier` al identificador universal de ese rol.
|
||||
|
||||
### Hello World example
|
||||
### Ejemplo Hello World
|
||||
|
||||
Explore a minimal, end-to-end example that demonstrates objects, functions, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
Explora un ejemplo mínimo de extremo a extremo que demuestra objetos, funciones y múltiples desencadenadores [aquí](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
## Manual setup (without the scaffolder)
|
||||
## Configuración manual (sin el generador)
|
||||
|
||||
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire scripts in your package.json:
|
||||
Aunque recomendamos usar `create-twenty-app` para la mejor experiencia de inicio, también puedes configurar un proyecto manualmente. No instales la CLI globalmente. En su lugar, agrega `twenty-sdk` como dependencia local y conecta scripts en tu package.json:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
```
|
||||
|
||||
Then add scripts like these:
|
||||
Luego agrega scripts como estos:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -510,13 +691,13 @@ Then add scripts like these:
|
||||
}
|
||||
```
|
||||
|
||||
Now you can run the same commands via Yarn, e.g. `yarn dev`, `yarn sync`, etc.
|
||||
Ahora puedes ejecutar los mismos comandos mediante Yarn, p. ej., `yarn app:dev`, `yarn app:sync`, etc.
|
||||
|
||||
## Troubleshooting
|
||||
## Solución de problemas
|
||||
|
||||
* Authentication errors: run `yarn auth` and ensure your API key has the required permissions.
|
||||
* Cannot connect to server: verify the API URL and that the Twenty server is reachable.
|
||||
* Types or client missing/outdated: run `yarn generate` and then `yarn dev`.
|
||||
* Dev mode not syncing: ensure `yarn dev` is running and that changes are not ignored by your environment.
|
||||
* Errores de autenticación: ejecuta `yarn auth:login` y asegúrate de que tu clave de API tenga los permisos necesarios.
|
||||
* No se puede conectar al servidor: verifica la URL de la API y que el servidor de Twenty sea accesible.
|
||||
* Tipos o cliente faltantes/obsoletos: ejecuta `yarn app:generate` y luego `yarn app:dev`.
|
||||
* El modo de desarrollo no sincroniza: asegúrate de que `yarn app:dev` esté ejecutándose y de que los cambios no sean ignorados por tu entorno.
|
||||
|
||||
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
Canal de ayuda en Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
---
|
||||
title: Webhooks
|
||||
description: Receive real-time notifications when events occur in your CRM.
|
||||
description: Recibe notificaciones en tiempo real cuando ocurran eventos en tu CRM.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
Webhooks push data to your systems in real-time when events occur in Twenty — no polling required. Use them to keep external systems in sync, trigger automations, or send alerts.
|
||||
Los webhooks envían datos a tus sistemas en tiempo real cuando ocurren eventos en Twenty — no se requiere sondeo. Úsalos para mantener sincronizados los sistemas externos, activar automatizaciones o enviar alertas.
|
||||
|
||||
## Create a Webhook
|
||||
## Crear un Webhook
|
||||
|
||||
1. Go to **Settings → APIs & Webhooks → Webhooks**
|
||||
2. Click **+ Create webhook**
|
||||
3. Enter your webhook URL (must be publicly accessible)
|
||||
4. Click **Save**
|
||||
1. Ve a **Configuración → APIs y Webhooks → Webhooks**
|
||||
2. Haga clic en **+ Crear webhook**
|
||||
3. Introduce la URL de tu webhook (debe ser públicamente accesible)
|
||||
4. Haga clic en **Guardar**
|
||||
|
||||
The webhook activates immediately and starts sending notifications.
|
||||
El webhook se activa de inmediato y comienza a enviar notificaciones.
|
||||
|
||||
<VimeoEmbed videoId="928786708" title="Creating a webhook" />
|
||||
<VimeoEmbed videoId="928786708" title="Crear un webhook" />
|
||||
|
||||
### Manage Webhooks
|
||||
### Gestionar Webhooks
|
||||
|
||||
**Edit**: Click the webhook → Update URL → **Save**
|
||||
**Editar**: Haz clic en el webhook → Actualizar la URL → **Guardar**
|
||||
|
||||
**Delete**: Click the webhook → **Delete** → Confirm
|
||||
**Eliminar**: Haz clic en el webhook → **Eliminar** → Confirmar
|
||||
|
||||
## Events
|
||||
## Eventos
|
||||
|
||||
Twenty sends webhooks for these event types:
|
||||
Twenty envía webhooks para estos tipos de eventos:
|
||||
|
||||
| Event | Example |
|
||||
| ------------------ | ---------------------------------------------------------- |
|
||||
| **Record Created** | `person.created`, `company.created`, `note.created` |
|
||||
| **Record Updated** | `person.updated`, `company.updated`, `opportunity.updated` |
|
||||
| **Record Deleted** | `person.deleted`, `company.deleted` |
|
||||
| Evento | Ejemplo |
|
||||
| ---------------------------- | ---------------------------------------------------------- |
|
||||
| **Se crea un registro** | `person.created`, `company.created`, `note.created` |
|
||||
| **Se actualiza un registro** | `person.updated`, `company.updated`, `opportunity.updated` |
|
||||
| **Se elimina un registro** | `person.deleted`, `company.deleted` |
|
||||
|
||||
All event types are sent to your webhook URL. Event filtering may be added in future releases.
|
||||
Todos los tipos de eventos se envían a la URL de tu webhook. Es posible que se agregue el filtrado de eventos en versiones futuras.
|
||||
|
||||
## Payload Format
|
||||
## Formato de la carga útil
|
||||
|
||||
Each webhook sends an HTTP POST with a JSON body:
|
||||
Cada webhook envía una solicitud HTTP POST con un cuerpo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -55,35 +55,35 @@ Each webhook sends an HTTP POST with a JSON body:
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ----------- | ------------------------------------------------ |
|
||||
| `event` | What happened (e.g., `person.created`) |
|
||||
| `data` | The full record that was created/updated/deleted |
|
||||
| `timestamp` | When the event occurred (UTC) |
|
||||
| Campo | Descripción |
|
||||
| ----------------- | -------------------------------------------------- |
|
||||
| `evento` | Qué ocurrió (p. ej., `person.created`) |
|
||||
| `datos` | El registro completo que se creó/actualizó/eliminó |
|
||||
| `marca de tiempo` | Cuándo ocurrió el evento (UTC) |
|
||||
|
||||
<Note>
|
||||
Respond with a **2xx HTTP status** (200-299) to acknowledge receipt. Non-2xx responses are logged as delivery failures.
|
||||
Responde con un **estado HTTP 2xx** (200-299) para confirmar la recepción. Las respuestas que no sean 2xx se registran como errores de entrega.
|
||||
</Note>
|
||||
|
||||
## Webhook Validation
|
||||
## Validación de Webhook
|
||||
|
||||
Twenty signs each webhook request for security. Validate signatures to ensure requests are authentic.
|
||||
Twenty firma cada solicitud de webhook por seguridad. Valida las firmas para garantizar que las solicitudes sean auténticas.
|
||||
|
||||
### Headers
|
||||
### Encabezados
|
||||
|
||||
| Header | Description |
|
||||
| ---------------------------- | --------------------- |
|
||||
| `X-Twenty-Webhook-Signature` | HMAC SHA256 signature |
|
||||
| `X-Twenty-Webhook-Timestamp` | Request timestamp |
|
||||
| Encabezado | Descripción |
|
||||
| ---------------------------- | ------------------------------- |
|
||||
| `X-Twenty-Webhook-Signature` | Firma HMAC SHA256 |
|
||||
| `X-Twenty-Webhook-Timestamp` | Marca de tiempo de la solicitud |
|
||||
|
||||
### Validation Steps
|
||||
### Pasos de validación
|
||||
|
||||
1. Get the timestamp from `X-Twenty-Webhook-Timestamp`
|
||||
2. Create the string: `{timestamp}:{JSON payload}`
|
||||
3. Compute HMAC SHA256 using your webhook secret
|
||||
4. Compare with `X-Twenty-Webhook-Signature`
|
||||
1. Obtén la marca de tiempo de `X-Twenty-Webhook-Timestamp`
|
||||
2. Crea la cadena: `{timestamp}:{JSON payload}`
|
||||
3. Calcula HMAC SHA256 usando tu secreto de webhook
|
||||
4. Compara con `X-Twenty-Webhook-Signature`
|
||||
|
||||
### Example (Node.js)
|
||||
### Ejemplo (Node.js)
|
||||
|
||||
```javascript
|
||||
const crypto = require("crypto");
|
||||
@@ -101,12 +101,12 @@ const expectedSignature = crypto
|
||||
const isValid = expectedSignature === req.headers["x-twenty-webhook-signature"];
|
||||
```
|
||||
|
||||
## Webhooks vs Workflows
|
||||
## Webhooks vs flujos de trabajo
|
||||
|
||||
| Method | Direction | Use Case |
|
||||
| ---------------------------- | --------- | ---------------------------------------------------------- |
|
||||
| **Webhooks** | OUT | Automatically notify external systems of any record change |
|
||||
| **Workflow + HTTP Request** | OUT | Send data out with custom logic (filters, transformations) |
|
||||
| **Workflow Webhook Trigger** | IN | Receive data into Twenty from external systems |
|
||||
| Método | Dirección | Caso de uso |
|
||||
| --------------------------------------------- | --------- | --------------------------------------------------------------------------------- |
|
||||
| **Webhooks** | SALIDA | Notificar automáticamente a los sistemas externos cualquier cambio en un registro |
|
||||
| **Flujo de trabajo + solicitud HTTP** | SALIDA | Enviar datos con lógica personalizada (filtros, transformaciones) |
|
||||
| **Disparador de webhook de flujo de trabajo** | ENTRADA | Recibir datos en Twenty desde sistemas externos |
|
||||
|
||||
For receiving external data, see [Set Up a Webhook Trigger](/l/es/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).
|
||||
Para recibir datos externos, consulta [Configurar un disparador de webhook](/l/es/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).
|
||||
|
||||
Reference in New Issue
Block a user