Migrating from API v1¶
API v1 was removed in Novelty 1.0.0
Novelty 1.0.0 and later serve only API v2, and every request under /api/v1/ returns 410 Gone. Move each v1 integration to v2 before you upgrade from Novelty 0.x.
Novelty API v2 introduces improvements to response formats, error handling, endpoint organization, and role-based access control. This guide explains the key changes and how to migrate your integrations from v1 to v2.
Why V2?¶
The v1 API evolved organically as Novelty grew, resulting in inconsistencies that made the API harder to learn and use. V2 is a ground-up redesign that pins every decision to Google's API Improvement Proposals (AIPs) — the same external design rules Google uses for its own public APIs. Anchoring v2 to a published standard means the rationale for every URL shape, status code, and wire format lives in one citable place rather than tribal knowledge, and well-known client patterns (pagination cursors, RFC 3339 timestamps, structured errors) work without per-endpoint surprises.
Novelty is a JSON HTTP API, not a Protobuf/gRPC service, so a few AIP details are assumed by their gRPC tooling rather than written into the proposal text. Where an AIP relies on Protobuf semantics that don't apply to a JSON API (for example, the proto3 default-value-omission rule, or carrying google.protobuf.Any payloads inside an error response), v2 keeps the shape and intent of the AIP but adapts the encoding for plain JSON. Those deviations are called out next to the relevant principle below.
At a glance, v2 is focused on:
-
Predictable patterns — Consistent naming conventions and HTTP method usage across all endpoints
-
Model-scoped operations — Ingest, scoring, and transformation endpoints are scoped under a named model (
model/{modelName}), making multi-model support explicit in the URL - RPC-style actions — Action endpoints use colon-separated verbs (
:record,:learn,:shutdown) to clearly distinguish actions from resource operations - Better error handling — Structured error responses with actionable messages
- Fine-grained access control — Role-based permissions that map cleanly to organizational responsibilities
API Version Overview¶
| Version | Status | Base Path | Notes |
|---|---|---|---|
| v2 | Current (default) | /api/v2/ |
Default API for all installations |
| v1 | Removed | /api/v1/ |
Removed in Novelty 1.0.0; every request returns 410 Gone |
Novelty 1.0.0 and later serve only v2. The tables below map each v1 endpoint from Novelty 0.x to its v2 replacement.
Design Principles¶
V2 follows REST conventions more strictly than v1. Each principle below cites the AIP that codifies it; the AIP itself is the authoritative source for edge cases and rationale.
Model-scoped resources (AIP-121 resource-oriented design, AIP-122 resource names) — Operations on ingests, scoring, and transformations are scoped under /model/{modelName}/ in the URL path. Novelty supports multiple named models. There is no default model — you must create one before recording observations. Scoping at the URL level (rather than via a query parameter) makes the parent–child relationship part of the resource name, so each resource has one canonical URL.
RPC-style action verbs (AIP-136 custom methods) — Action endpoints use a colon-separated verb suffix (e.g., model/{modelName}:batchRecord, system:shutdown). The colon syntax keeps actions on the same path as the resource they act on without misrepresenting them as sub-resources, so URL trees stay shaped around nouns while still naming verbs explicitly.
camelCase path segments (AIP-122 resource names) — Path segments use camelCase (slidingWindows, shardSizeLimits, systemInfo) instead of kebab-case (sliding-window, shard-sizes, system-info). This matches the camelCase JSON field convention from AIP-140, so an identifier looks the same whether it appears in a URL or a request body.
System endpoints at top level — Administrative endpoints are grouped under /system/ (renamed from /admin/). Treating "system" as a top-level category (rather than a peer of every per-model collection) keeps /model/{modelName}/… reserved for model-scoped resources.
Plural resource names (AIP-122 resource names) — Collection endpoints use plural nouns (/ingests not /ingest) so the URL itself signals "this addresses many" vs. /ingests/{ingestName} for "this addresses one."
Resource names in request body (AIP-133 standard Create) — When creating resources, the name is part of the resource representation in the request body, not the URL. The server validates the complete resource definition (name + configuration) atomically before accepting it, and the same JSON document round-trips through GET/PUT/POST without callers having to splice the name in and out of the URL.
POST for actions, PUT for idempotent updates (AIP-133 / AIP-134 / AIP-136) — PUT is reserved for idempotent operations where repeating the request produces the same result; DELETE removes a resource and is also idempotent.
Endpoint Path Changes¶
The tables below show the mapping from v1 to v2 endpoints. All v2 paths are relative to /api/v2/.
System Endpoints¶
System endpoints (formerly "admin") manage system configuration, monitoring, and cluster operations. These have moved from /admin/ to /system/ and path segments now use camelCase.
| v1 Endpoint | v2 Endpoint | Notes |
|---|---|---|
GET /admin/build-info |
GET /system/systemInfo |
Renamed, moved to /system/ |
GET /admin/config |
GET /system/config |
Moved to /system/ |
| — | GET /system/licenseUsage |
New |
GET /admin/liveness |
GET /system/liveness |
Moved to /system/ |
GET /admin/metrics |
GET /system/metrics |
Moved to /system/ |
GET /admin/readiness |
GET /system/readiness |
Moved to /system/ |
| — | GET /system/roles |
New |
POST /admin/shard-sizes |
GET /system/shardSizeLimits |
Split into GET, moved, camelCase |
POST /admin/shard-sizes |
PATCH /system/shardSizeLimits |
Split into PATCH, moved, camelCase |
POST /admin/shutdown |
POST /system:shutdown |
RPC-style verb |
Ingest Endpoints¶
Ingest (novelty stream) endpoints manage data streaming into Novelty. In v2, ingest operations are scoped under a named model (/model/{modelName}/ingests). When creating a stream, the stream name moved from the URL path to the request body, and the model, which v1 took in the body as noveltyNamespace, moved to the URL path.
| v1 Endpoint | v2 Endpoint | Notes |
|---|---|---|
GET /novelty/stream |
GET /model/{modelName}/ingests |
Model-scoped, restructured |
POST /novelty/stream/{name} |
POST /model/{modelName}/ingests |
Name to body, model to path |
GET /novelty/stream/{name} |
GET /model/{modelName}/ingests/{ingestName} |
Model-scoped, param renamed |
DELETE /novelty/stream/{name} |
DELETE /model/{modelName}/ingests/{ingestName} |
Model-scoped, param renamed |
Scoring Endpoints¶
Scoring endpoints record and query observations. In v2, these are scoped under a named model and use RPC-style verbs. There is no default model — you must create one before recording observations.
| v1 Endpoint | v2 Endpoint | Notes |
|---|---|---|
POST /novelty/{namespace}/observe |
POST /model/{modelName}:record |
Renamed, model-scoped |
POST /novelty/{namespace}/read |
POST /model/{modelName}:read |
Model-scoped, RPC verb |
POST /novelty/{namespace}/remove |
POST /model/{modelName}:remove |
Model-scoped, RPC verb |
POST /novelty/{namespace}/observe/bulk |
POST /model/{modelName}:batchRecord |
Model-scoped, RPC verb |
POST /novelty/{namespace}/read/bulk |
POST /model/{modelName}:batchRead |
Model-scoped, RPC verb |
POST /novelty/{namespace}/remove/bulk |
POST /model/{modelName}:batchRemove |
Model-scoped, RPC verb |
GET /novelty/{namespace}/statistics |
— | Removed |
Sliding Window Endpoints¶
| v1 Endpoint | v2 Endpoint | Notes |
|---|---|---|
GET /novelty/{namespace}/sliding-window/config |
GET /model/{modelName}/slidingWindows |
Model-scoped, camelCase |
PUT /novelty/{namespace}/sliding-window/config |
PUT /model/{modelName}/slidingWindows |
Model-scoped, camelCase |
DELETE /novelty/{namespace}/sliding-window |
DELETE /model/{modelName}/slidingWindows |
Model-scoped, camelCase |
Transformation Endpoints¶
Transformations define how raw observations are processed before scoring. In v2, transformations are scoped under a named model.
| v1 Endpoint | v2 Endpoint | Notes |
|---|---|---|
GET /novelty/transforms |
GET /model/{modelName}/transformations |
Model-scoped |
PUT /novelty/transforms/{transformation} |
POST /model/{modelName}/transformations |
Model-scoped, name in body, POST |
GET /novelty/transforms/{transformation} |
GET /model/{modelName}/transformations/{transformationName} |
Model-scoped, param renamed |
DELETE /novelty/transforms/{transformation} |
DELETE /model/{modelName}/transformations/{transformationName} |
Model-scoped, param renamed |
PUT /novelty/transforms/{transformation}/learn |
POST /model/{modelName}/transformations:learn |
RPC verb, name in body, POST |
POST /novelty/test-transform |
POST /model/{modelName}/transformations/{transformationName}:evaluate |
Renamed, model-scoped |
Model Namespace Endpoints¶
Models (formerly "contexts") are isolated environments for scoring observations. Model management endpoints have moved to /system/models. There is no default model — you must create one explicitly.
| v1 Endpoint | v2 Endpoint | Notes |
|---|---|---|
| — | GET /system/models |
New |
| — | POST /system/models |
New |
DELETE /novelty/{namespace} |
DELETE /system/models/{modelName} |
Moved to /system/models |
Key Changes¶
This section describes behavioral changes that affect how you interact with the API, regardless of which endpoints you use.
Response Format¶
Both v1 and v2 return data directly at the top level of the response body. List endpoints in v2 return a paginated envelope with items and nextPageToken fields per AIP-158 (pagination). v2 201 responses may include a Warning header with advisory messages.
The envelope wraps every list response over a user-managed collection (models, transformations) even when server-side paging is not currently implemented for that endpoint — the wrapper itself is the forward-compatibility contract. Changing from a bare array to an envelope later would be breaking, so v2 ships the envelope up front and nextPageToken is simply omitted when there are no further pages.
Two list endpoints return other shapes: GET /model/{modelName}/ingests returns an object keyed by ingest name, and GET /system/roles returns a bare array.
See Response Format for the complete response specification.
Error Responses¶
v1 used varied error formats depending on the error type. v2 returns a unified ApiError envelope shaped after AIP-193 / google.rpc.Status:
{
"error": {
"code": 404,
"status": "NOT_FOUND",
"message": "Ingest Stream, my-ingest, does not exist in model 'vastdata'",
"details": []
}
}
The error object includes code (the HTTP status code), status (the canonical AIP-193 status string such as INVALID_ARGUMENT or NOT_FOUND), message (the primary human-readable error), and details (additional structured context — request IDs, hints, machine-readable error classifications). Pinning the wire format to AIP-193 means clients can parse errors with one branch of code and get a stable status field for switching, instead of grepping message strings.
Because Novelty is a JSON API rather than a Protobuf service, the details[] entries are modelled as a closed, type-discriminated union (RequestInfo, Help, ErrorInfo) instead of AIP-193's open google.protobuf.Any carrier. The shape mirrors google.rpc.ErrorInfo and friends for familiarity, but new variants are added by extending the union rather than packing arbitrary proto messages.
See Error Responses for the complete error format specification.
Strict Request Validation¶
As of Novelty 1.1.0, v2 rejects request bodies containing unrecognized fields with a 400 INVALID_ARGUMENT error naming the offending field and listing the valid ones. v1, and v2 before Novelty 1.1.0, silently ignored unknown fields, so misspelled optional settings that appeared to work there (while silently not applying) fail now. This applies to both JSON and YAML request bodies.
Enum Wire Format¶
Sealed-trait enumerations encode to SCREAMING_SNAKE_CASE strings per AIP-126 (e.g. RUNNING, INVALID_ARGUMENT). The PascalCase identifiers stay in idiomatic Scala source; conversion happens at the codec boundary. Where the wire value must mirror an external system (e.g. Kafka's PLAINTEXT/latest), the codec uses an explicit literal instead of the AIP-126 default — those cases are documented at the field.
Type discriminators on sum types — the "type": "<Kind>" field on ingest, output, and credential configurations — are a deliberate exception: discriminator values use bare PascalCase names ("type": "Kinesis", not "type": "KINESIS" or "type": "KinesisIngest"). AIP-126 covers enum values, not ADT type tags, and PascalCase discriminator values read more naturally next to the Scala class names they identify.
Role-Based Access Control¶
v2 introduces role-based permissions. Each endpoint requires specific capabilities, and predefined roles (SuperAdmin, SRE, Architect, DataEngineer, Analyst, Billing) grant sets of capabilities.
See OIDC/RBAC Setup for roles, capabilities, and configuration details.
Resource Creation Pattern¶
API v2 uses a consistent pattern for creating resources where the resource name is in the request body rather than the URL path, and the resource is scoped to a model:
v1:
POST /api/v1/novelty/stream/my-ingest-name
Content-Type: application/json
{ "noveltyNamespace": "mynamespace", "inputStream": { "type": "FileInput", "filePath": "..." }, ... }
v2:
POST /api/v2/model/{modelName}/ingests
Content-Type: application/json
{ "name": "my-ingest-name", "transformation": "my-transformation", "source": { "type": "File", "path": "...", "format": { "type": "JsonL" } } }
This applies to ingests, transformations, and models.
Migration Checklist¶
- Update base paths —
/admin/is now/system/; data operations are now under/model/{modelName}/ - Update to camelCase — Path segments changed from kebab-case to camelCase (e.g.,
sliding-window→slidingWindows) - Adopt RPC-style verbs — Action endpoints use colon verbs (e.g.,
/novelty/{namespace}/observe/bulk→/model/{modelName}:batchRecord) - Update HTTP methods — Some endpoints changed from PUT/GET to POST
- Move resource names to body — For create operations (ingests, transformations, models)
- Update parameter names —
name→ingestName,namespace→modelName,transformation→transformationName - Update query parameter formats —
at-time→atTime(RFC 3339 timestamp),timeout→timeout(duration string like20s) - Update error handling — Parse the new structured
ApiErrorformat withcode,status,message,details - Remove unrecognized request fields — v2 rejects request bodies containing unknown fields (v1 silently ignored them)
- Configure authentication — Ensure proper role assignments for RBAC
Related Documentation¶
- REST API Reference - Interactive API documentation
- OIDC/RBAC Setup - Authentication and authorization