Coming in Broadcast 3.0. Until then, this page applies only to the Upcoming (edge) build.

API Endpoints Hook

Use this hook to let another system call your plugin. For example, your shop can tell Broadcast about a new order.

This is a sensitive permission. The admin sees Its own API.

How it works

The address of your endpoint is:

https://your-broadcast-server/api/plugins/<plugin id>/<endpoint name>

Broadcast checks the API token first, and then calls your function. The token needs two things:

  1. The permission that the endpoint names in the manifest.
  2. The endpoint’s name in the token’s Plugin endpoints field. Write local.order-tagger/orders, one on each line, or local.order-tagger/* for all endpoints of that plugin.

So an existing token cannot call your endpoint until someone allows it by name.

Full example: tag buyers

manifest.json:

{
  "id": "local.order-tagger",
  "name": "Order tagger",
  "version": "1.0.0",
  "requires": { "broadcast": ">=3.0.0 <4.0.0", "pluginApi": "1" },
  "permissions": {
    "subscribers": "read",
    "tags": "write"
  },
  "hooks": {
    "apiEndpoints": [
      { "name": "orders", "method": "POST", "permission": "subscribers_write" }
    ]
  }
}

dist/main.js:

export default definePlugin({
  apiEndpoints: {
    async orders(request, ctx) {
      const { email, product } = request.body ?? {};
      if (!email || !/^[a-z0-9-]{1,50}$/.test(product ?? "")) {
        return { status: 422, body: { error: "Send an email and a product code." } };
      }

      const subscriber = await ctx.broadcast.subscribers.find(email);
      if (!subscriber) return { status: 404, body: { error: "No such subscriber." } };

      await ctx.broadcast.tags.add({ subscriber_id: subscriber.id, tags: [`bought:${product}`] });
      return { status: 201, body: { tagged: true } };
    },
  },
});

Call it with a channel API token:

curl -X POST https://your-broadcast-server/api/plugins/local.order-tagger/orders \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email": "ada@example.com", "product": "pro"}'

The answer is {"tagged":true} with status 201. An admin API token must also send broadcast_channel_id. See API Authentication.

What you get

{
  "name": "orders",
  "method": "POST",
  "query": {},
  "body": { "email": "ada@example.com", "product": "pro" },
  "token_label": "My shop"
}

body is the parsed JSON, or the raw text if it is not JSON, or null. It can be up to 1 MB. query holds the query string.

What you return

Return { status, body } to choose the HTTP status. Return any other JSON value, and it is the body with status 200.

Errors the caller can see

Status Answer Reason
404 {"error":"Not found"} The plugin is off in this channel, or there is no such endpoint
405 {"error":"Method not allowed"} The endpoint uses another method
403 {"error":"This token may not call this plugin endpoint."} The token lacks the permission, or the endpoint is not in its list
413 {"error":"The request body is too large."} The body is over 1 MB
502 {"error":{"code":"plugin_error","message":"..."}} Your code threw, or it took too long

A complete example

The official Polls plugin has an API with five endpoints, input checks and an idempotency key. See Polls API.

Endpoints have fixed names, with no path parameters. Pass an ID in the query or the body.

Common mistakes

You trust the input. A token only proves who called. Check each field, as the example does for product.

You mix up the two permissions. permission in the endpoint is what the caller’s token needs. permissions at the top of the manifest is what the plugin needs. This example needs both.

You expect subscribers.find to throw. It returns null when there is no such person, so check for that.

You forget that a caller can retry. Networks fail, and a caller can send the same request twice. Make your work safe to repeat, or use an idempotency_key built from the caller’s order number.

Last updated

Was this page helpful?

Thanks for your feedback!

Thanks for letting us know. We'll work on improving this page.