Core Concepts and Domain Model
Project / Channel / Participant / Task / Callback fields and invariants, plus the two credential families.
This page explains, once, the terms, fields and invariants that recur throughout the documentation. Field names follow the frozen domain interface definition: the platform API (/v1/*) uses camelCase wire fields, while the Agora-compatible endpoints use snake_case aliases.
The two credential families
| Credential | Shape | Purpose | Where it lives |
|---|---|---|---|
| Project credential | Authorization: Basic base64(customerId:customerSecret) | Server to server: signing tokens, channel management, recording / media push, telemetry ingestion | In your backend only |
| Console session | Authorization: Bearer ses_* | Console BFF: projects, key rotation, service activation, callback configuration, audit | Console pages (12-hour TTL) |
| App ID | 32 hexadecimal characters | Client initialization and path parameters (/dev/v1/channel/{appid}) | Visible to clients |
| App Certificate | 64 hexadecimal characters | HMAC key for signing and verifying tokens | Server side only, or only as a secret-manager reference |
Project
| Field | Description |
|---|---|
id | prj_*, the project primary key |
appId | 32 hex characters, the project identifier visible to clients |
customerId / customerSecretHash | Basic user name; the password is stored as SHA-256 only |
appCertificate | Token signing key (stored hashed or in controlled storage; the plaintext is returned once) |
authMode | token (default, clients present a token) or appid |
status | active / suspended; anything other than active returns 403 app_disabled |
tokenTtlSeconds | Upper bound of the token lifetime policy (default 3600) |
privilegeTtlSeconds | Upper bound of the privilege window (default 3600); the effective value is min(ttl, this value) |
services | { recording, streaming, analytics } service switches |
quotas | { recordingConcurrent, streamingConcurrent, maxChannels }, defaulting to 5 / 5 / 0 (0 = unlimited channels) |
Channel and Participant
- Channel name: 1–128 characters from
[A-Za-z0-9_-], case-sensitive, and unique within a project. - Media-plane room names are namespaced by App ID:
<appId>:<channelName>, so tenants cannot collide. profileiscommunicationorliveand decides whether the Agora-compatible endpoints returnusersorbroadcasters/audience.- A Participant is "one online presence of one user inside one channel":
uid(1..2147483647), optionaluserId(IM identity),role,platform,joinedAt/leftAt,leaveReason,audioMuted/videoMuted,publishing.{audio,video,screen}.
Platform codes (platform) match Agora: 0 other, 1 Android, 2 iOS, 5 Windows, 6 Linux, 7 Web, 8 macOS.
Leave reasons (leaveReason / callback reason) map as follows: 1 normal/other, 2 timeout, 3 kicked or banned, 5 duplicate login/switch, 10 network disconnected.
Task
Recording and media push are both task-shaped resources with their own state machine:
starting → running → stopping → stopped
↘ failed
↘ expired (media push only: triggered by idleTimeout)- Every state change is persisted first, then written to the Outbox, and only then delivered as a callback (transactional outbox), so a lost callback never rolls back state — it only causes a redelivery.
RecordingTaskadditionally carrieslayout,storage,subscribeAudioUids/subscribeVideoUids,files[],usageSeconds,queueWaitMs,idempotencyKey.StreamingTaskadditionally carriesentryPoint,streamName,rtmpUrl,hlsUrl,idleTimeoutSeconds,lastActiveAt.
UsageEvent and QualitySample
- Usage is billed per minute; types are
audio/video/recording/streaming/transcoding. Every event carries anidempotencyKey, so duplicate observations over the webhook and reconciliation channels are never billed twice. - A quality sample is a per-user, per-minute aggregate:
videoFreezeMs/audioFreezeMs/networkDelayMsare the raw freeze and delay durations; thresholds are applied on the server (see Analytics). - Six analysis dimensions:
region,networkType,osType,sdkVersion,deviceType,channelSize.
CallbackEnvelope
| Field | Description |
|---|---|
noticeId | Unique notification id per event; usable for idempotency |
productId | Always 1 (RTC) |
eventType | Numeric event code (101–112, 201–203), see Callback events |
notifyMs | When the event happened (epoch milliseconds) |
sid | Event session id |
payload | Event-specific fields |
Platform API vs. Agora-compatible API
The same business facts, in two envelopes:
| Dimension | Platform API (/v1/*) | Agora-compatible (/dev/v1/*, /v1/apps/*, /v1/projects/*) |
|---|---|---|
| Field naming | camelCase | snake_case (channel_name, user_count, cname, resourceId…) |
| List envelope | { total, pageNo, pageSize, channels } | { success: true, data: { channels, total_size } } |
| Task result | The full task resource (state machine, files, queue diagnostics) | { resourceId, sid, serverResponse } |
| Errors | { code, message, reason, sdkCode } | Same (either reading habit still gets you the reason) |
| Best for | Our SDKs / console / new integrations | Migrating from Agora with the fewest path changes |
Key invariants
rtcUid ∈ [1, 2147483647]and is never 0 (in Agora semantics 0 means "assigned randomly by the server"); the same(appId, userId)is stable for the duration of a session.- Channel names are unique within a project; the SFU room name is
<appId>:<channelName>. - A token is bound to
(appId, channelName, uid, role, privilegeExpireAt, tokenExpireAt); a wildcard token uses*as its channel. - Every task state change is persisted first, then enters the Outbox, and is delivered as a callback last.
- A
join_channelban applies in two places at once: token issuance (403banned) and live sessions (the user is disconnected, with leave reason 3). - Usage events are idempotent on
(projectId, idempotencyKey); the webhook and reconciliation channels write through the same idempotent path. - Callbacks carry both a SHA1 (
Agora-Signature) and a SHA256 (Agora-Signature-V2) signature, computed over the raw request body bytes.