What it allows
- Publications: a communication in a channel, with a title, a summary and a body in Markdown or HTML. It is saved as a draft, published now or scheduled, and it can be withdrawn, archived, unarchived and reactivated.
- Highlights: a short message with a severity, a validity window and an audience, which can point to a publication or a channel, or stand alone. It is saved as a draft, activated now or scheduled, and it can be resolved and reactivated.
- A content preview, which shows how the text is converted and which highlights and campaigns publishing it would affect.
- Reading the granted channels and audiences, and the objects the integration itself created.
Recipients, read receipts and company members do not exist in this API, and there are no routes to delete objects: an object is withdrawn or resolved and keeps its identity.
Authentication and grant
Requests authenticate like those of the Data API: an OAuth 2.0 token or an API key, sent as Authorization: Bearer. The read:communications scope reads the channels, the audiences and the own objects. The write:communications scope also writes and does not inherit publishing: publishing is a separate grant.
The scope is not enough. The company administrator grants the integration, in Empresa › Integraciones of the application, the data permission of the communications domain. That permission requires the confidential field group and, to name people in an audience, also people. Without the permission or the scope, the API responds 403 insufficient_scope. Whoever grants or widens the permission needs the equivalent permissions in the application for what is delegated.
The grant is read again on every request: whatever is revoked stops applying immediately. A missing extension is the same as an empty one and everything is denied. Whatever is omitted takes the value that allows the least.
Field paths
A write permission carries paths of the closed registry communications/1. Without explicit paths only the draft paths are granted. The last three also require can_publish.
| Path | What it allows | Requires can_publish |
|---|---|---|
publications.content | Create and edit publications as drafts, and the content preview. Granted by default. | No |
publications.publish | Publish or schedule a publication, or reactivate it. | Yes |
highlights.content | Create and edit highlights as drafts. Granted by default. | No |
highlights.activate | Activate or schedule a highlight, or reactivate it. It also requires highlights.content. | Yes |
lifecycle | Withdraw, archive and unarchive publications, and resolve highlights. | Yes |
What the grant limits
| Permission field | Effect |
|---|---|
allowed_channel_ids (up to 100) | Channels the integration writes to. It is also the only set of channels it can use as the target of a highlight. |
allowed_reader_group_ids (up to 100) | Reader groups it can use in a selected audience. |
allowed_user_ids (up to 200) | People it can use in a selected audience. It requires the people field group. |
can_publish | Enables the publishing paths. |
max_severity | Maximum severity of a highlight: info, important, critical, in that order. By default, info. |
can_create_standalone_highlights | Standalone highlights, with no publication or channel target. |
allow_channel_audience | channel audience in a highlight with a target. It requires at least one granted channel. |
allow_tenant_audience | tenant audience in a standalone highlight. It requires can_create_standalone_highlights. |
Routes
All of them hang from https://daas.masnet.com/api/v1/integrations/data/communications. The idempotency key is mandatory on every write, except on the content preview.
| Method and route | Use | Field path | Success |
|---|---|---|---|
GET /channels | Granted channels: id, name and archived. | Read | 200 |
GET /audiences | Available audience modes (selected, channel, tenant) and the opaque ref references of the granted groups and people. It does not include members. | Read | 200 |
POST /content-preview | Converts the content and computes its digest. It does not write. | publications.content | 200 |
PUT /publications/{external_id} | Creates or updates a publication. | publications.content, and with state: published, also publications.publish | 201 on create, 200 on update |
GET /publications/{external_id} | Reads an own publication. | Read | 200 |
POST /publications/{external_id}/withdraw | Withdraws the publication. | lifecycle | 200 |
POST /publications/{external_id}/archive | Archives a published publication. | lifecycle | 200 |
POST /publications/{external_id}/unarchive | Returns an archived publication to published. | lifecycle | 200 |
POST /publications/{external_id}/reactivate | Reactivates a withdrawn publication. | publications.content and publications.publish | 200 |
PUT /highlights/{external_id} | Creates or updates a highlight. | highlights.content, and with state: active, also highlights.activate | 201 on create, 200 on update |
GET /highlights/{external_id} | Reads an own highlight. | Read | 200 |
POST /highlights/{external_id}/resolve | Resolves the highlight: it withdraws it without deleting it. | lifecycle | 200 |
POST /highlights/{external_id}/reactivate | Reactivates an expired or resolved highlight. | highlights.content and highlights.activate | 200 |
Bodies are JSON and are rejected if they carry an unknown field. The commands (withdraw, archive, unarchive, resolve) carry the body {}. A field about company, authorship, environment, ownership, privileges or permissions is rejected with its own code and is not ignored.
Every successful response carries the object in data and, when it applies, the ETag header. A creation also responds Location, and the repetition of an operation already applied responds Idempotency-Replayed: true. An error carries forbidden_field, unknown_field or another code from Errors.
Identity and concurrency
The external_id in the path is the stable identifier of the object in your system: 1 to 128 ASCII characters that start with a letter or digit and allow . _ : - (^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$). It identifies the object within the namespace of the integration, the environment and the kind: two integrations can use the same external_id, and an object of another integration or company responds 404 unavailable, the same as a nonexistent one.
| Mechanism | How it is used |
|---|---|
source_revision | An integer greater than or equal to 1 that your system increases with every change of the object. The API stores it as the last accepted revision. |
Idempotency-Key | 8 to 128 characters A-Z a-z 0-9 . _ : -, one per intent. The same key with the same request (route, body and preconditions) returns the previous result without repeating the effect and sets Idempotency-Replayed: true. With another request it responds 409 idempotency_conflict. It is kept for 72 hours. |
If-None-Match: * | Mandatory to create. Without it, 428 precondition_required. |
If-Match | Mandatory to update and for every command, with the current etag of the object (the ETag header or the etag member). Another value responds 409 version_conflict. |
What a PUT responds
| Situation | Result |
|---|---|
| The object does not exist | With If-None-Match: *, it creates it and responds 201. Without it, 428 precondition_required. |
source_revision greater than the last accepted one | With If-Match and the current etag, it applies the change and responds 200. With If-None-Match, 409 already_exists. |
Same source_revision and same body | It returns the previous result without writing. |
Same source_revision and another body | 409 source_revision_conflict, with details.current_source_revision. |
Lower source_revision | 409 stale_source_revision, with details.current_source_revision. |
| The object is withdrawn, resolved or expired | 409 terminal_state. A PUT does not reactivate it: use the reactivate command. |
| A person took control of the object | 409 manual_control, with no effect. |
On a timeout or a 503, repeat the request with the same Idempotency-Key. Locks and serialization failures of the database respond 503 temporarily_unavailable with Retry-After: 5 and are also retried with the same key. See idempotency_key_required, idempotency_conflict, version_conflict, source_revision_conflict, stale_source_revision.
Publications
| Field | Description |
|---|---|
source_revision | Required. An integer greater than or equal to 1. |
channel_id | Required. UUID of a granted channel. It does not change after the publication is created (409 immutable_channel). |
title | Required. 1 to 160 characters. |
summary | Optional. Up to 400 characters. If omitted, it is derived from the content. |
reference_date | Optional. Calendar date YYYY-MM-DD. |
content | Required. { "format": "markdown" | "html", "value": "…" }. See Content. |
state | draft (the default) saves a draft. published saves the draft and publishes it in the same operation. |
scheduled_at | RFC 3339 instant with an explicit zone. Only with state: published. If it is more than 5 seconds ahead, the publication is scheduled and publishes itself. Otherwise it publishes immediately. A publication that is already published or archived cannot be scheduled. |
preview_digest | Digest of 64 hexadecimal characters from the preview. Required when publishing again a publication that has affected highlights or campaigns. If sent, it must match the content, the external_id and the source_revision. |
confirm_linked_highlight_effects | true when critical highlights or highlights that require a read confirmation are affected. |
accept_lossy_conversion | true to publish content that was converted with warnings. |
A PUT with state: draft only writes the draft: the visible edition of an already published publication does not change until it is published. The response distinguishes the published edition, the draft and the scheduled one, each with title, summary, reference_date, revision_digest, origin and the warnings of the conversion.
States and commands
| Operation | From | To |
|---|---|---|
PUT with state: draft | No object, or any state except withdrawn | draft if it did not exist. Otherwise the same state, with an edition in draft |
PUT with state: published | No object, draft, published, archived or blocked | published, or scheduled with a future scheduled_at |
archive | published | archived |
unarchive | archived | published |
withdraw | published, scheduled, blocked or archived | withdrawn |
reactivate | withdrawn | published |
- The possible states are
draft,scheduled,published,archived,withdrawnandblocked. A command that does not fit the state responds409 invalid_transition. - Withdrawing a publication also withdraws the active or scheduled highlights that point to it and blocks the Surveys campaigns that use it as context.
reactivatecarries the same body as thePUTwithoutstate, withIf-Matchand a greatersource_revision, and requires content later than the withdrawal (409 new_revision_required).- Publishing again a publication that has active highlights requires the
preview_digestand, if any is critical or requests a read confirmation,confirm_linked_highlight_effects: true. If today's grant does not cover those highlights, the response is403 linked_highlight_effect_not_authorized.
Content and preview
content.value is Markdown or HTML. The API converts it to a tree of communication_nodes/1 nodes, which is what people see, and returns the nodes, the warnings and the digests in the preview. HTML is sanitized: nothing active is kept.
| Allowed | Detail |
|---|---|
| Blocks | Paragraphs, headings of level 2 to 4, ordered and unordered lists, quotes and code blocks. |
| Inline | Text, emphasis, strong, code and links. |
| Links | Only absolute https:// addresses without credentials, up to 2,048 characters. Any other form responds 422 unsafe_link. |
| Rejected | HTML with active elements, such as script, style, iframe, form or svg, or with event attributes or dangerous addresses (422 unsafe_html). |
Whatever has no equivalent is not rejected: it is converted with a warning. A conversion with warnings is lossy: true and is only published with accept_lossy_conversion: true.
| Warning | When it appears |
|---|---|
heading_level_adjusted | A heading outside the allowed levels is adjusted. |
image_removed | An image was removed. |
table_flattened | A table was converted to text. |
unsupported_element | An element with no equivalent was dropped. detail names the tag. |
list_start_ignored | An ordered list did not start at 1. |
link_title_removed | The title of a link was removed. |
nested_link_flattened | A link inside another link was converted to text. |
control_characters_removed | Control characters were removed. |
Preview
POST /content-preview receives the same content as a publication PUT plus the external_id, and does not write. It returns the nodes of the body (body), the warnings with their code and detail, lossy, the revision_digest of the content and the preview_digest that binds that content to the identity, the revision and today's effects. If publishing would renew a publication with linked highlights or campaigns, it reports linked_highlight_effects (with external_id, severity and requires_ack), affected_count, requires_effect_confirmation and affected_campaigns.
| Content limit | Value |
|---|---|
Input text (content.value) | 100 KiB in UTF-8 (413 content_too_large) |
| Tree nodes | 2,000 (422 too_many_nodes) |
| Nesting | 16 levels (422 too_deep) |
| Serialized tree | 256 KiB (422 tree_too_large) |
Highlights
| Field | Description |
|---|---|
source_revision | Required. An integer greater than or equal to 1. |
state | draft (the default) saves a draft. active activates it in the same operation. |
title | Required. 1 to 80 characters. |
message | Required. 1 to 240 characters. |
severity | info (the default), important or critical. It cannot exceed the granted max_severity. |
priority | Integer from -1,000 to 1,000. By default, 0. |
target | Absent or null for a standalone highlight. { "kind": "communication_channel", "channel_id": "…" } points to a granted channel. { "kind": "communication_publication", "external_id": "…" } points to a publication of the same integration and environment, and the channel is deduced from it. |
audience | Required. { "mode": "channel" } inherits the audience of the channel and only applies with a target. { "mode": "tenant" } is for the whole company and only applies to a standalone highlight. { "mode": "selected", "group_refs": […], "user_refs": […] } uses the references from GET /audiences, 1 to 100 in total. |
starts_at, ends_at | Required. RFC 3339 instants with an explicit zone. The end is after the start and the window lasts at most 90 days, 30 if the severity is critical. |
requires_ack | true requests a read confirmation. By default, false. |
- With
state: activeand a paststarts_at, the highlight becomes active. With a futurestarts_atit is scheduled and activates itself. With a pastends_at, the response is422 window_elapsed. - A
PUTwithstate: draftsaves the edition without changing the one people see. - The possible states are
draft,scheduled,active,expired,withdrawnandblocked. The view distinguishes theactiveedition and thedraft, and each carriesmaterial_digestandorigin. resolvewithdraws an active, scheduled or blocked highlight without deleting it and keeps its identity.reactivatecarries the same body as thePUTwithoutstate, withIf-Matchand a greatersource_revision, and only applies fromexpiredorwithdrawn.
Frequent errors: severity_not_granted, audience_not_granted, standalone_not_granted, invalid_target, target_unavailable, invalid_window, audience_required.
Control and revocation
Every object reports control: integration while the integration governs it and local if a person took control in the application. With control: local, every external write responds 409 manual_control and has no effect. A person can return control from the application: the last accepted source_revision becomes the floor, and only a strictly greater revision moves forward.
- Removing the Communications permission or revoking the integration releases its objects: they pass to local control, whatever was scheduled becomes
blocked(blocked_reasongrant_removedorintegration_revoked), and the identity and the history are kept. Removing a channel releases only what belongs to that channel (channel_removed). - Granting the permission again does not reclaim what was released: the integration receives
409 manual_controluntil a person returns control. - The system revalidates against today's grant whatever the integration left scheduled before each effect. If the grant no longer covers it, the object is blocked with
authority_lostorlinked_effect_not_authorizedand is never activated with an authority wider than the current one.
Test integrations
A test-environment integration works in its own space, which people never see. It can only preview content, read its own objects and save drafts. Publishing, scheduling, activating, withdrawing, resolving, archiving, unarchiving and reactivating respond 403 test_environment before taking effect. The environment comes from the credential: an mk_test_… API key or a client of a test integration works in this space.
Limits and quotas
| Scope | Limit | When exceeded |
|---|---|---|
| Request body | 160 KiB | 413 payload_too_large |
| Writes per client | 60 per minute | 429 rate_limited with Retry-After: 60 |
| Writes per company, adding up all its integrations | 300 per minute | 429 rate_limited with Retry-After: 60 |
| New objects per integration, drafts included | 600 per hour | 429 object_quota_exceeded with Retry-After: 600 |
| Live linked objects per integration | 20,000 | 409 binding_limit_reached |
| Published, archived or scheduled publications per company | 2,000 | 409 tenant_content_limit |
| Active or scheduled highlights per company | 100 | 409 tenant_content_limit |
The per-minute quotas are measured with a shared counter and do not pass unmeasured: if the counter does not respond, the request is rejected with 503 quota_unavailable and Retry-After: 30, it is not executed and has no effect. Retry with the same Idempotency-Key. The other caps are measured in the database. Reads follow the common quota of the Data API.
If the company is in read-only mode for maintenance, every new write responds 503 communications_read_only with Retry-After: 60, with no effect. If the company plan does not include Communications, the response is 403 module_unavailable.
Errors
Errors use application/problem+json (RFC 9457) with type, title, status, detail, code and correlation_id. Some add a details member: fields (up to five fields), field, max, current_source_revision, allowed or required. The detail never repeats the content sent and never reveals data of another company.
The complete list, with the cause and the action of each code, is in Errors.
Examples
The identifiers, the dates and the digests are examples. The digests of the publication were computed with the API code over the content shown.
1. Preview the content
POST /api/v1/integrations/data/communications/content-preview HTTP/1.1
Host: daas.masnet.com
Authorization: Bearer <token>
Content-Type: application/json
{
"external_id": "erp-aviso-2026-0142",
"source_revision": 1,
"channel_id": "3f2b9a52-6d41-4c63-9a0e-7b1d5c8e2f10",
"title": "Cierre anticipado de despacho",
"summary": "El viernes 6 de noviembre el despacho cerrará a las 15:00.",
"reference_date": "2026-11-06",
"content": { "format": "markdown", "value": "## Horario\nRecibiremos pedidos hasta las **15:00**." }
}{
"data": {
"external_id": "erp-aviso-2026-0142",
"source_revision": 1,
"title": "Cierre anticipado de despacho",
"summary": "El viernes 6 de noviembre el despacho cerrará a las 15:00.",
"reference_date": "2026-11-06",
"body": {
"format": "communication_nodes",
"version": 1,
"nodes": [
{ "type": "heading", "level": 2, "children": [{ "type": "text", "text": "Horario" }] },
{
"type": "paragraph",
"children": [
{ "type": "text", "text": "Recibiremos pedidos hasta las " },
{ "type": "strong", "children": [{ "type": "text", "text": "15:00" }] },
{ "type": "text", "text": "." }
]
}
]
},
"warnings": [],
"lossy": false,
"revision_digest": "c6e43004945b4ba969ada3d4fef798db024dc00d086c9a3b7785f34e4b959208",
"preview_digest": "cfc609c35176ee914dc1db9f77d35a2a6113f02361f16462974c27f0ab277a8a",
"linked_highlight_effects": [],
"affected_count": 0,
"requires_effect_confirmation": false,
"affected_campaigns": 0,
"environment": "production"
}
}2. Create and publish the publication
It is created with If-None-Match: * and published in the same operation. The ETag of the response is the etag that later changes will require.
PUT /api/v1/integrations/data/communications/publications/erp-aviso-2026-0142 HTTP/1.1
Host: daas.masnet.com
Authorization: Bearer <token>
Idempotency-Key: 6f1d2c9e-4b7a-4c1e-9d55-0a1b2c3d4e5f
If-None-Match: *
Content-Type: application/json
{
"source_revision": 1,
"channel_id": "3f2b9a52-6d41-4c63-9a0e-7b1d5c8e2f10",
"title": "Cierre anticipado de despacho",
"summary": "El viernes 6 de noviembre el despacho cerrará a las 15:00.",
"reference_date": "2026-11-06",
"content": { "format": "markdown", "value": "## Horario\nRecibiremos pedidos hasta las **15:00**." },
"state": "published"
}HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
ETag: "mK3vQ9xT1uYw7Rz2LpA8cE"
Location: /api/v1/integrations/data/communications/publications/erp-aviso-2026-0142
{
"data": {
"external_id": "erp-aviso-2026-0142",
"kind": "publication",
"channel_id": "3f2b9a52-6d41-4c63-9a0e-7b1d5c8e2f10",
"status": "published",
"blocked_reason": null,
"control": "integration",
"environment": "production",
"source_revision": 1,
"etag": "\"mK3vQ9xT1uYw7Rz2LpA8cE\"",
"published": {
"title": "Cierre anticipado de despacho",
"summary": "El viernes 6 de noviembre el despacho cerrará a las 15:00.",
"reference_date": "2026-11-06",
"revision_digest": "c6e43004945b4ba969ada3d4fef798db024dc00d086c9a3b7785f34e4b959208",
"origin": "integration",
"warnings": []
},
"draft": null,
"scheduled": null,
"first_published_at": "2026-11-04T14:32:08.114Z",
"published_at": "2026-11-04T14:32:08.114Z",
"archived_at": null,
"withdrawn_at": null,
"updated_at": "2026-11-04T14:32:08.114Z"
}
}3. Create a highlight that points to the publication
PUT /api/v1/integrations/data/communications/highlights/erp-incidencia-2026-0031 HTTP/1.1
Host: daas.masnet.com
Authorization: Bearer <token>
Idempotency-Key: 9a4c7e12-3d58-4b60-8f1a-c2e5d7b09a36
If-None-Match: *
Content-Type: application/json
{
"source_revision": 1,
"state": "active",
"title": "Despacho con retraso en la Región de Valparaíso",
"message": "Los despachos a la Región de Valparaíso se entregarán con un día de retraso.",
"severity": "important",
"priority": 10,
"target": { "kind": "communication_publication", "external_id": "erp-aviso-2026-0142" },
"audience": { "mode": "channel" },
"starts_at": "2026-11-04T15:00:00-03:00",
"ends_at": "2026-11-06T18:00:00-03:00",
"requires_ack": false
}HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
ETag: "Qe5nB2sXc8TgV0hJrW1dYa"
Location: /api/v1/integrations/data/communications/highlights/erp-incidencia-2026-0031
{
"data": {
"external_id": "erp-incidencia-2026-0031",
"kind": "highlight",
"status": "active",
"blocked_reason": null,
"control": "integration",
"environment": "production",
"source_revision": 1,
"etag": "\"Qe5nB2sXc8TgV0hJrW1dYa\"",
"active": {
"title": "Despacho con retraso en la Región de Valparaíso",
"message": "Los despachos a la Región de Valparaíso se entregarán con un día de retraso.",
"severity": "important",
"priority": 10,
"target": { "kind": "communication_publication", "channel_id": "3f2b9a52-6d41-4c63-9a0e-7b1d5c8e2f10", "external_id": "erp-aviso-2026-0142" },
"audience": { "mode": "channel", "group_refs": [], "user_refs": [] },
"starts_at": "2026-11-04T18:00:00.000Z",
"ends_at": "2026-11-06T21:00:00.000Z",
"requires_ack": false,
"material_digest": "5be21f0c7a94d3860e1c47b2f9a8d6053ce1b7490d2f58a6e3c0b9174d8a2f61",
"origin": "integration"
},
"draft": null,
"activated_at": "2026-11-04T18:04:51.307Z",
"withdrawn_at": null,
"updated_at": "2026-11-04T18:04:51.307Z"
}
}4. Withdraw the publication
A command carries If-Match with the current etag and the body {}. Withdrawing the publication also withdraws the highlight that points to it.
POST /api/v1/integrations/data/communications/publications/erp-aviso-2026-0142/withdraw HTTP/1.1
Host: daas.masnet.com
Authorization: Bearer <token>
Idempotency-Key: d3f08a57-61b2-4e94-a7c0-1e9b5f2c8d47
If-Match: "mK3vQ9xT1uYw7Rz2LpA8cE"
Content-Type: application/json
{}A change with an older revision
HTTP/1.1 409 Conflict
Content-Type: application/problem+json; charset=utf-8
{
"details": { "current_source_revision": 3 },
"type": "https://masnet.com/developers/errors/stale_source_revision",
"title": "Conflicto",
"status": 409,
"detail": "El recurso cambió o la operación no corresponde a su estado.",
"code": "stale_source_revision",
"correlation_id": "4c9e1d7a-52b8-4f03-9a6e-d18b0c3f5e72"
}Quota unavailable
HTTP/1.1 503 Service Unavailable
Content-Type: application/problem+json; charset=utf-8
Retry-After: 30
{
"type": "https://masnet.com/developers/errors/quota_unavailable",
"title": "Cuota no disponible",
"status": 503,
"detail": "No se pudo medir la cuota de escritura; la solicitud no se ejecutó.",
"code": "quota_unavailable",
"correlation_id": "b27e90c4-1d63-4a58-8f0b-7c5a2e9d1f36"
}