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.
- Create an Agent AppOpen a Workplace in the Developer Console, add an Agent App, and link only the Members it should serve.
- Define Job typesAdd at least one
agent_to_membertype for Agent-created work and onemember_to_agenttype if people can initiate work. - Issue a credentialSelect the minimum scopes your integration needs. The secret is shown once; never expose it in a browser or mobile build.
- 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.
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: Agent <credential_id>.<ar_live_secret>Available scopes
member:readjob:createjob:readjob:closejob:reopencomment:createcomment:readattachment:createattachment:readThey 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.
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_refis your stable business identifier and supports safe replay.Idempotency-Keyis required on mutations and must be 16–128 characters.type_keymust 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.
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.
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 '{}'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.
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
readybefore 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.
{"type":"airelay.webhook.verify","challenge":"verify_01EXAMPLE"}Delivery contract
X-Airelay-TimestampUnix seconds used in signature and replay checksX-Airelay-Signaturev1-prefixed HMAC-SHA256 digestX-Airelay-DeliveryStable delivery ID used for deduplicationX-Airelay-AttemptDiagnostic retry attempt number{
"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.
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-Deliverybefore applying an event. - Return the previously recorded success for a duplicate delivery ID.
- Return any
2xxonly 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.
| Method | Path | Purpose |
|---|---|---|
| GET | /members | List members linked to this Agent App |
| GET | /job-types | List configured Job types |
| POST | /jobs | Create an Agent-to-member Job |
| GET | /jobs/{jobId} | Read a Job and its current version |
| POST | /jobs/{jobId}/comments | Add a comment or reply |
| POST | /jobs/{jobId}/close | Close an Agent-created Job |
| POST | /jobs/{jobId}/reopen | Reopen an Agent-created Job |
| POST | /attachments | Upload a file for malware scanning |
Errors use application/problem+json and include a stable code, HTTP status, request_id, trace_id, and structured details.