Authentication and conventions
Conventions of the management API: bearer authentication, roles and capabilities, idempotent mutations, pagination and the error envelope.
- Availability: Planned
- Evidence: Read from source
- Reference
Authentication
Every request carries the bearer token of the shared Beyond authority, the same identity Workspace uses:
GET /v1/session HTTP/1.1
Authorization: Bearer <token>The CDN keeps no accounts and no memberships. An organization is a Workspace team, and the CDN stores only opaque organization and user identifiers. Guest tokens of private applications are never accepted by this API.
| Operation | Request | Capability | Retry |
|---|---|---|---|
session.read |
GET /v1/session |
Any signed-in user | |
organization.read |
GET /v1/organizations/{organization} |
application.read |
session.read answers the signed-in user, its organizations, roles, CDN capabilities and a plan summary. organization.read answers the context of one organization of the caller. When the authority cannot be reached the answer is 502 UPSTREAM_UNAVAILABLE, never a refusal or an approval.
Roles and capabilities
Roles come from the authority: owner, admin, developer, viewer and none. The CDN owns a table of capabilities over those roles, and each operation names the capability it requires.
| Capability | Allows | owner | admin | developer | viewer |
|---|---|---|---|---|---|
application.read |
Read applications, targets, registrations, graphs, inventories, jobs, releases, environments, domains and backend closures | Yes | Yes | Yes | Yes |
application.manage |
Create, change and delete applications and their targets, and bind an application to a Beyond project or release it | Yes | Yes | Yes | |
release.prepare |
Register selections, prepare candidates, cancel jobs and bind the testing environment |
Yes | Yes | Yes | |
release.promote |
Bind production: promote, roll back and retire releases |
Yes | Yes | ||
access.manage |
Change visibility and manage guest grants | Yes | Yes | ||
domain.manage |
Reserve the testing subdomain and manage custom domains | Yes | Yes | ||
providers.manage |
Read the credential-free summary of the organization's registry providers, and set or remove their settings and write-only credentials | Yes | Yes | ||
usage.read |
Read usage, quotas, plan, entitlements, credit and ledger | Yes | Yes | Yes | |
plan.request |
Join or leave the premium waitlist | Yes | Yes | ||
events.subscribe |
Obtain realtime grants, replay events and read snapshots | Yes | Yes | Yes | Yes |
The role none has no capability and hides the organization's resources.
Two operations require a capability that depends on the environment they act on: promotion and rollback need release.prepare for testing and release.promote for production. The contract states this in a machine-readable form, x-beyond-capability-by, next to x-beyond-capability.
Refusals follow one rule. A resource the caller cannot see answers 404 NOT_FOUND, so its existence is not disclosed. A visible resource the role cannot act on answers 403 FORBIDDEN.
Operations under /v1/platform belong to the platform backoffice. They require a platform operator capability that no organization role implies, and they answer NOT_FOUND to everyone else. They are not documented here.
Registry providers
An organization can resolve packages from registries other than the public one, for example a private registry for one npm scope. The settings belong to the organization and are used by the registrations that run afterwards. Pinned graphs and existing releases never change.
Version 1 reads npm-compatible registries that are reachable over the Internet. A registry reachable only through a private network or a VPN is outside version 1: the workers reach public Internet addresses only, and a registry address that cannot be one is refused when it is saved.
| Operation | Request | Capability | Retry |
|---|---|---|---|
providers.list |
GET /v1/organizations/{organization}/providers |
providers.manage |
|
providers.set |
PUT /v1/organizations/{organization}/providers/{provider} |
providers.manage |
natural |
providers.remove |
DELETE /v1/organizations/{organization}/providers/{provider} |
providers.manage |
natural |
{provider} is default, for every package without a scope setting, or an npm scope such as @acme.
{ "registry": "https://<registry host>", "token": "<registry token>" }| Member | Required | Rule |
|---|---|---|
registry |
Yes | The base address, http or https, without user information, query or fragment. It may carry a path prefix. An address of a loopback, private, link-local or other reserved range, or a name only a local resolver answers (localhost, *.internal, a single label), is 400 VALIDATION_FAILED with the message must be a public Internet address. An address this version cannot use as an npm-compatible registry answers 422 UNSUPPORTED_INPUT. |
token |
No | The bearer token of the registry. Bearer is the only authentication mode of this version. A request without token leaves the registry without a credential. |
Removing a provider makes the packages of that scope resolve from the default registry again. Removing an absent provider answers the same.
Visibility belongs to the package
Whether a package is public is decided for each package, not by the credential it was read with:
- A package read without a credential is public.
- A package read with a credential is public only when the same registry, asked anonymously, answers the same version with the same integrity and the same archive, and the archive itself answers anonymously. Any doubt keeps it private: a registry answers "not found" and "not allowed" alike to an anonymous client.
- A Git repository or an archive URL read with a credential is private.
A public package produces public outputs, even when your token was used to read it, and it is downloaded without the token. A private package stays in your organization, and so does every output that reads it, including a public package's module that imports it. A private application does not make its public dependencies private. Removing a token does not make bytes that were already retained public. See Preparation and inventory.
Retries and concurrent changes
Each mutation states how it tolerates a retry:
| Kind | How it works | Used by |
|---|---|---|
key |
The Idempotency-Key header is required. A retry with the same key and the same request answers the original result without repeating the work. The same key with a different request is 409 IDEMPOTENCY_MISMATCH; a missing key is 400 IDEMPOTENCY_REQUIRED. |
Creating applications, registrations, preparations, custom domains and guest grants |
version |
The request carries expected_version. If it is not the current version of the resource, nothing changes and the answer is 409 CONFLICT_VERSION with details.current. |
Changing an application, promotion and rollback |
natural |
Repeating the request leaves the same state | Defining and removing targets, deleting, cancelling, retiring, reserving the testing subdomain, verifying a domain, revoking a grant, setting and removing a provider, asking for a member ticket |
An Idempotency-Key matches ^[A-Za-z0-9_.:-]{8,128}$, is chosen by the client, is unique per intended mutation and is scoped to the caller's organization. If a request times out, send it again with the same key: never generate a new key for a retry.
Pagination
List operations take limit (1 to 200, default 50) and after. A page answers items and, when there is more, next. Send next back as after. Cursors are opaque.
Identifiers
| Kind | Form |
|---|---|
| CDN resources (applications, releases, jobs, …) | <kind>_<random>, matching ^[a-z][a-z0-9]*_[A-Za-z0-9]{6,40}$, for example job_Prep0001x |
| Organizations and users | Opaque values owned by the authority. The CDN never parses them. |
| Times | RFC 3339 date-times |
| Digests | sha256- followed by 64 hexadecimal characters |
Errors
Failures use one envelope, shared in shape with the delivery contract. details replaces diagnostics:
{ "error": { "code": "CONFLICT_VERSION", "message": "…", "details": { "current": 7 } } }| Status | Code | Meaning |
|---|---|---|
400 |
VALIDATION_FAILED |
The request does not follow the schema; details.fields lists the problems |
400 |
IDEMPOTENCY_REQUIRED |
The mutation requires an Idempotency-Key header |
401 |
UNAUTHENTICATED |
No valid session accompanies the request |
402 |
CREDIT_INSUFFICIENT |
The organization's available credit cannot cover the reservation |
403 |
FORBIDDEN |
The resource is visible and the role lacks the capability |
403 |
ACCESS_REVOKED |
The session, membership, guest grant or realtime grant was revoked or expired |
403 |
ADMISSION_REQUIRED |
The caller is a member whose role allows the operation, but has not been admitted to CDN management and preparation, or the admission was revoked; details.policy names the admission policy. See Internal admission of people |
403 |
ENTITLEMENT_REQUIRED |
The organization's plan does not include the capability; details.entitlement names it |
404 |
NOT_FOUND |
The resource does not exist, or the caller may not know that it exists |
409 |
CONFLICT_VERSION |
expected_version is stale; details.current carries the current one |
409 |
IDEMPOTENCY_MISMATCH |
The key was already used with a different request |
409 |
STATE_INVALID |
The resource is not in a state that allows the operation |
409 |
NAME_CONFLICT |
The name is taken inside the organization |
409 |
DOMAIN_CONFLICT, DOMAIN_UNVERIFIED |
See Domains |
409 |
QUOTA_EXCEEDED |
A quota would be passed; details.quota names it |
409 |
PROJECT_CONFLICT |
The application is already bound to another Beyond project; details.current names it. See The project of an application |
409 |
PROJECT_INACTIVE |
The Beyond project is archived or deleted; details.state carries its state |
410 |
CURSOR_EXPIRED |
A replay cursor is older than the retained events |
413 |
PAYLOAD_TOO_LARGE |
The request body exceeds the accepted size |
422 |
UNSUPPORTED_INPUT |
A well-formed input this version cannot process: provider, publication form, format or dependency shape |
422 |
LIMIT_EXCEEDED |
The request exceeds a configured limit and is not retried automatically |
422 |
PROJECT_UNKNOWN |
The Beyond project does not exist or does not belong to the organization of the application; the two are never told apart |
501 |
NOT_IMPLEMENTED |
The operation exists in the contract and this service instance does not mount its area, or the integration it needs is not configured; details.operation names it |
429 |
RATE_LIMITED |
Too many requests; honor Retry-After |
500 |
INTERNAL |
Unexpected failure. The message never carries secrets. |
502 |
UPSTREAM_UNAVAILABLE |
A registry, the authority, Beyond Projects or another upstream did not answer. It is never a refusal or an approval |
503 |
BUDGET_EXHAUSTED |
The global admission budget does not accept new work now |
503 |
UNAVAILABLE |
The service cannot take the request now |
Codes are stable and never translated. The reasons a job can fail are a separate vocabulary that travels inside documents and events; see Jobs.
No GET starts work
No GET of this API, and no GET of published delivery, starts, resumes or schedules work. Only the explicit POST operations create jobs. Reading a job, a release or an inventory as often as you like is safe.