Zuplo API v4
Reference

Rule Index

Every rule has an ID. Lint messages cite it, and reviews should too. Each v4 rule ID links to the rule box on its page.

Lint

A rule in lint/ruleset.yaml fails CI on any finding. The chips name the rules.

Review

A person checks it in the pull request.

v4 rules

IDRuleEnforced by
V4-URL-1Paths start with /v4/; kebab-case segments; camelCase path parameters ending in Id, accounts and projects included.Lintv4-path-shape
V4-URL-2Only the URL shapes: a container, then one level of nesting under an owner, then re-root at the container.Lintv4-path-shape
V4-URL-3Collections are plural nouns; namespaces and singletons are registered words; no action verbs.Lintv4-path-shape
V4-URL-4One canonical URL per resource; resources with an ID live at /v4/{container}/{containerId}/{collection}/{id}, a bucket or project at the root at /v4/buckets/{bucketId}; one parameter name per collection.Lintv4-canonical-url
V4-URL-5A collection that creates resources has a canonical item URL for them, in the same container.Lintv4-canonical-url
V4-URL-6Accounts, projects and buckets are the containers; every path starts with one. Projects and buckets are listed and created in their account and addressed at the root by their ID; what a project deploys lives under /v4/projects/{projectId}, and gateway data under /v4/buckets/{bucketId}.Lintv4-path-shapeReview
V4-URL-7A collection name is one resource type across all of v4; product collections live in the product's namespace (…/ai/pools), never behind a prefix.Lintv4-path-shapev4-canonical-urlReview
V4-METH-1Fixed methods per URL shape; PUT only on singletons and association items; an association is added with a POST to its collection or set with a PUT on its item, never both.Lintv4-method-shape
V4-METH-2Fixed success codes; 201 has Location; 204 has no body; bodies are JSON with named schemas.Lintv4-success-responses
V4-IDEM-1GET, PUT, PATCH, DELETE and association upserts are retry-safe; PATCH sets absolute values; no side effects on GET.Review
V4-IDEM-2No operation declares Idempotency-Key: v4 does not support it.Lintv4-idempotency
V4-IDEM-3No operation declares If-Match, ETag or 412: v4 does not support them.Lintv4-conditional-requests
V4-IDEM-4When the client knows the identity, the request carries its key (in the body of a POST to the association collection, or the URL of a PUT on its item) and replaces an existing association.Review
V4-COLL-1Every list paginates with limit/offset and returns exactly { data, limit, offset, total, hasMore, next } as {Resource}List.Lintv4-list-envelope
V4-COLL-2include[] is the one parameter that adds to a response: an enum of nullable response properties, null unless asked for. No include*, with* or expand[] flags.Lintv4-parametersv4-enum-values
V4-ERR-1Every operation documents the errors it can return (400/401/403/404/429/default by shape, 409 where it can occur).Lintv4-error-responses
V4-NAME-1Schema, parameter and response names are PascalCase with acronyms written as words (AiPool, ApiKey; never AIPool).Lintv4-schema-names
V4-DATA-1No untyped JSON: types everywhere, typed maps, discriminated unions. A consumer's metadata is the one exception.Lintv4-typed-schemas
V4-DATA-2Money in US dollars, units in names (PerMillionTokens, Seconds).Review
V4-DATA-3Secrets are write-only on input and revealed once (…WithSecret), masked afterwards; only the person who holds a personal consumer can read its keys' secrets again (…/secret). Lint checks that responses have no writeOnly.Lintv4-response-schemasReview
V4-DATA-4Separate request (*Request, *Input, closed, bounded) and response (open, all required) schemas.Lintv4-request-schemasv4-response-schemas
V4-DATA-5People are always identified by their Zuplo user ID (user_…), through the shared UserId schema (userId, ownerId, …UserId); a sub is a read-only property (UserSub, or DeveloperPortalUserSub for developer portal users).Lintv4-user-ids
V4-OAS-1Summary at most 60 characters, a real description, exactly one declared tag; each tag in exactly one x-tagGroups group.Lintv4-operation-docsv4-document
V4-OAS-2operationIds derived from method and URL shape.Lintv4-operation-id
V4-OAS-3Bodies only on POST/PUT/PATCH, application/json, named {Verb}{Resource}Request; success bodies reference a named schema too.Lintv4-request-bodiesv4-success-responses
V4-OAS-4OpenAPI 3.1, the api.zuplo.com server, rate limits stated, shared components present.Lintv4-document
V4-OAS-5config/routes.oas.json is generated from spec/openapi.yaml, and every operation is a Zuplo route.Lintv4-zuplo-routeCI: check:routes

Shared rules, as applied in v4

IDShared ruleIn v4Enforced by
SCOPE-1..3Applicability, standards-only naming exceptions, queue endpointsAppliesReview
SCOPE-4GrandfatheringDoes not applyv4 has no grandfathered violations and no ignore filesCI
URL-1Kebab plural segments, camelCase parametersAppliesLintv4-path-shape
URL-2No audience-tier prefixesAppliesthere is no /internal or /admin in v4Lintv4-path-shape
URL-3Bare kebab verb actionsDoes not applyv4 has no actionsLintv4-path-shape
URL-4Short Stripe-like shapes; a parent is a filter, not a pathChangedflat inside the container (/v4/buckets/{bucketId}/ai/apps?poolId=…); only associations and credentials (…/users/{userId}/consumers) are nested (V4-URL-2/4/5)Lintv4-path-shapev4-canonical-url
URL-4aAccount-rooted, by-name paths are legacyChangedaccounts are a container, addressed by ID (/v4/accounts/{accountId}/buckets) (V4-URL-6)Lintv4-path-shapeReview
URL-5Webhook ingress prefixApplieswhen v4 receives webhooksReview
VER-1Path major version per subsystemChangedone /v4 for the whole APILintv4-path-shape
VER-2, VER-3, VER-4Deprecation, breaking changes, x-stabilityAppliesLintv4-documentReview
NAME-1camelCase propertiesAppliesstrictly (no baseURL)Lintv4-property-names
NAME-2-On timestampsAppliesboth ways (-On is always a timestamp)Lintv4-property-names
NAME-3kebab-case enumsAppliesLintv4-enum-values
NAME-4camelCase query parametersAppliesLintv4-parameters
NAME-5kebab verb-noun operationIdsNarrowedto V4-OAS-2Lintv4-operation-id
DATA-1RFC 3339 UTC date-timeAppliesLintv4-property-names
DATA-2null not omitted; PATCH tri-stateAppliesLintv4-response-schemasv4-request-schemas
DATA-3Prefixed opaque IDsChangedopaque IDs exactly as the owning service mints them, prefixed or not: accounts are UUIDs, projects prj_…, people user_… (V4-DATA-5), AI pools, apps and user apps config_…, environments and deployments unprefixed, built-in AI providers by name, custom ones aiprov_…, provider assignments aip_…Lintv4-property-namesReview (string IDs; that an ID is the service's in review)
COLL-1, COLL-2Envelope, limit boundsAppliesto every listLintv4-list-envelope
COLL-3Cursor for append-only collectionsApplieswhen they arriveReview
COLL-4, COLL-5sort, searchReservedLintv4-parameters (no synonyms)
COLL-6Reject unknown query parametersAppliesReviewruntime: request validation
EXP-1expand[]Changedinclude[] instead, and no other flags (V4-COLL-2)Lintv4-parameters
ERR-1..3Problem JSON, title/detail, zup.fail typesAppliesLintv4-error-responsesv4-documentReview
ERR-4application/problem+jsonAppliesLintv4-error-responses
ERR-5401 / 403 / 404 semanticsAppliesLintReview (WWW-Authenticate)
ERR-6Errors documentedWidenedto V4-ERR-1Lintv4-error-responses
AUTH-1, AUTH-2Bearer only; security declaredAppliesLintv4-securityv4-parameters
METH-1, METH-2PATCH/PUT semantics, status codesNarrowedto V4-METH-1/2Lint
METH-3Create-then-poll LROsAppliesReview
ROB-1Idempotency-KeyDoes not applynot applied: v4 has no Idempotency-Key, so no operation declares it (V4-IDEM-2)Lintv4-idempotency
ROB-2ETag / If-MatchDoes not applynot applied: v4 has no conditional requests and the last write wins, so no operation declares them (V4-IDEM-3)Lintv4-conditional-requests
ROB-3Rate-limit contractAppliesLintv4-error-responsesv4-document (429 + Retry-After)
ROB-4Bounded, closed requestsApplieswith separate schemas (V4-DATA-4)Lintv4-request-schemas
ROB-5..7Request ID, bulk, outbound webhooksAppliesReviewruntime
SPEC-1Specs generated from codeChangedv4 is design-first (spec/openapi.yaml)CI: check:routes
SPEC-2..4x-internal/x-zuplo-route, lint gate, descriptionsAppliesLint
Last modified on