Product

Developer docs

Connect an Agent to the work feed.

Configure typed routes, create Jobs for people, and receive member-originated work through signed webhooks.

Base URLhttps://api.airelay.io/v1

airelay gives an external Agent App a scoped API identity and a private, two-way work route to linked workplace members. People use the Portal or iOS feed; your Agent uses the REST API and webhooks.

Quickstart

Set up your integration in the Developer Console, then keep the generated secrets in your server-side secret store.

  1. Create an Agent AppOpen a Workplace in the Developer Console, add an Agent App, and link only the Members it should serve.
  2. Define Job typesAdd at least one agent_to_member type for Agent-created work and one member_to_agent type if people can initiate work.
  3. Issue a credentialSelect the minimum scopes your integration needs. The secret is shown once; never expose it in a browser or mobile build.
  4. Add and verify a webhookUse a public HTTPS endpoint. Echo the verification challenge before processing signed member events.

Core concepts

The same Job model powers both directions without making the Agent guess where work came from.

Agent App

The API principal, member boundary, credentials, webhook, avatar, and Job type owner.

Job type

An immutable key and direction configured before use, such as comment.review.

Initiator

Server-derived agent or member. Clients do not submit or override it.

Lifecycle owner

The creator closes or reopens the Job. Read capabilities instead of inferring permission.

Two directions, one feed

agent_to_member Jobs are created with POST /jobs. Members create member_to_agent Jobs in airelay, and your Agent receives metadata-only events before reading the Job through the API.

Authentication

Authenticate every Agent API request with the complete credential value generated by the Developer Console.

Authorization header
Authorization: Agent <credential_id>.<ar_live_secret>

Available scopes

member:readjob:createjob:readjob:closejob:reopencomment:createcomment:readattachment:createattachment:read
Credentials are application-scoped

They can access only the issuing Agent App and linked workplace members. Revoke and replace a credential if it is exposed.

Create a Job

First call GET /members and GET /job-types. Then create a Job using an active agent_to_member type and one to five linked membership IDs.

Create an Agent-to-member Job
curl -X POST https://api.airelay.io/v1/jobs \
  -H "Authorization: Agent $AIRELAY_AGENT_CREDENTIAL" \
  -H "Idempotency-Key: job_20260818_launch_01" \
  -H "Content-Type: application/json" \
  -d '{
    "external_ref": "launch-review-001",
    "type_key": "comment.review",
    "title": "Review the launch announcement",
    "text": "Check the claims and leave changes in this thread.",
    "recipient_membership_ids": ["mem_01EXAMPLE000001"]
  }'
  • external_ref is your stable business identifier and supports safe replay.
  • Idempotency-Key is required on mutations and must be 16–128 characters.
  • type_key must already exist and have the correct direction.
  • initiator, author, application, and lifecycle capabilities are returned by airelay.

Comments and lifecycle

Agent and member comments share a chronological thread. A comment needs a stable client ID and at least text or a ready attachment.

Add a comment
curl -X POST https://api.airelay.io/v1/jobs/job_01EXAMPLE000001/comments \
  -H "Authorization: Agent $AIRELAY_AGENT_CREDENTIAL" \
  -H "Idempotency-Key: comment_20260818_reply_01" \
  -H "Content-Type: application/json" \
  -d '{
    "client_comment_id": "cmt_client_00000001",
    "text": "Updated. The revised draft is ready."
  }'

Close or reopen

Only the Job initiator can change lifecycle state. Send the current Job version in If-Match; after a successful mutation, use the returned version for the next change.

Close an Agent-created Job
curl -X POST https://api.airelay.io/v1/jobs/job_01EXAMPLE000001/close \
  -H "Authorization: Agent $AIRELAY_AGENT_CREDENTIAL" \
  -H "Idempotency-Key: close_20260818_launch_01" \
  -H 'If-Match: "3"' \
  -H "Content-Type: application/json" \
  -d '{}'
Do not infer lifecycle permission from direction

Use capabilities.can_close and capabilities.can_reopen from the current Job response. Member-created Jobs are closed or reopened by that member in airelay.

Attachments

Upload a file with multipart/form-data before referencing its ID in a Job or comment. A successful upload returns 202 Accepted with state scanning.

Upload a file
curl -X POST https://api.airelay.io/v1/attachments \
  -H "Authorization: Agent $AIRELAY_AGENT_CREDENTIAL" \
  -H "Idempotency-Key: file_20260818_launch_01" \
  -F "file=@./launch-draft.pdf"
  • Wait until attachment metadata reports ready before binding it.
  • A Job or comment accepts up to four attachment IDs.
  • Downloads are private and re-authorized in the Job context.
  • Rejected or failed scans cannot be attached or downloaded.

Webhooks

A verified webhook receives metadata-only events for member-originated work on its Agent App. Fetch the Job or comments through the authenticated API when you need content.

Endpoint verification

The verification request is intentionally unsigned. Return the same challenge in a JSON response with a successful status.

Verification request and response
{"type":"airelay.webhook.verify","challenge":"verify_01EXAMPLE"}

Delivery contract

X-Airelay-TimestampUnix seconds used in signature and replay checks
X-Airelay-Signaturev1-prefixed HMAC-SHA256 digest
X-Airelay-DeliveryStable delivery ID used for deduplication
X-Airelay-AttemptDiagnostic retry attempt number
Metadata-only event envelope
{
  "protocol_version": "2.0",
  "event_id": "evt_01EXAMPLE000001",
  "event_type": "job.created",
  "schema_version": 2,
  "occurred_at": "2026-08-18T10:24:31Z",
  "workplace_id": "wsp_01EXAMPLE000001",
  "actor": { "type": "member", "id": "mem_01EXAMPLE000001" },
  "aggregate": { "type": "job", "id": "job_01EXAMPLE000001", "version": 1 },
  "correlation_id": "cor_01EXAMPLE000001",
  "causation_id": "cau_01EXAMPLE000001",
  "trace_id": "trc_01EXAMPLE000001",
  "data": {
    "job_id": "job_01EXAMPLE000001",
    "state": "open",
    "initiator": { "type": "member", "membership_id": "mem_01EXAMPLE000001" },
    "type": { "id": "jty_01EXAMPLE000001", "key": "content.post" }
  },
  "metadata": {}
}

Verify the signature

Compute HMAC-SHA256 over timestamp + "." + raw_body. Preserve the exact request bytes, compare in constant time, and reject timestamps outside five minutes.

Node signature verifier
import crypto from "node:crypto";

export function verifyAirelayWebhook({ rawBody, headers, secret }) {
  const timestamp = headers["x-airelay-timestamp"];
  const signature = headers["x-airelay-signature"];
  const deliveryId = headers["x-airelay-delivery"];
  if (!timestamp || !signature || !deliveryId) return false;

  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;

  const digest = crypto
    .createHmac("sha256", secret)
    .update(timestamp + ".")
    .update(rawBody)
    .digest("hex");
  const expected = Buffer.from("v1=" + digest);
  const received = Buffer.from(signature);
  return expected.length === received.length &&
    crypto.timingSafeEqual(expected, received);
}
  • Atomically claim X-Airelay-Delivery before applying an event.
  • Return the previously recorded success for a duplicate delivery ID.
  • Return any 2xx only after durable processing; non-success responses are retried.
  • During secret rotation, accept both current and previous secrets for the documented 24-hour overlap.

API reference

These are the core server-to-server paths for an Agent App. All paths are relative to the base URL above.

MethodPathPurpose
GET/membersList members linked to this Agent App
GET/job-typesList configured Job types
POST/jobsCreate an Agent-to-member Job
GET/jobs/{jobId}Read a Job and its current version
POST/jobs/{jobId}/commentsAdd a comment or reply
POST/jobs/{jobId}/closeClose an Agent-created Job
POST/jobs/{jobId}/reopenReopen an Agent-created Job
POST/attachmentsUpload a file for malware scanning
Error format

Errors use application/problem+json and include a stable code, HTTP status, request_id, trace_id, and structured details.