Outcome
Create a Consent project in an existing verified workspace, with its own public key, default purpose set and initial configuration. This is creation, despite the action id projects.configure; it is not reconfiguration of an existing key.
Action: app_topolo_consent.projects.configure.
POST /api/projects
Before you start
Verify original production principal/organization, current Consent workspace read_write-or-higher grant and app_topolo_consent.workspace:write. Discover the actual canonical workspace first and bind it separately. This action creates a project inside that existing verified workspace; it does not provision an Auth workspace or organization.
Approve the project’s intended name, public key, configuration environment metadata and region mode. Inspect current projects/defaults and existing tenant identity. A caller-supplied authOrgId does not override an ordinary organization-scoped principal; do not broaden context to use a platform-only source branch.
All identifiers and responses below are fictional source-derived fixtures and UNRUN. English labels come from immutable source catalog data under an explicit English-fixture premise, not a verified deployed translation release. No actual project/tenant/workspace/config/SDK/notification or provider/model action was executed.
Request and effects
Discover the current action, validate the reviewed input and inspect its plan with the actual workspace separately bound. Confirm project and shared tenant/configuration effects. Fictional ids/keys below authorize no execution.
The schema accepts optional name,projectKey,authOrgId,tenantName,environment,mode and no extra fields, but source requires a nonblank trimmed name or a usable normalized key. A schema-valid empty/whitespace-only intent can return400 project_name_required. It also needs an actual resolved workspace id and owning organization context.
Explicit keys pass safeKey: lowercase, replace unsupported runs with hyphens, trim leading/trailing hyphens and require at most80 characters. A normalized explicit key must be free across Consent, not merely within this organization: owner lookup is global. If unusable but a name is present, source derives a key from tenant name plus project display name instead. Always use the actual returned project.key, not the exact input spelling.
Name-derived candidates are truncated to80 and checked for availability, then suffixes2..9 are tried. The final stable-hash fallback is returned without another availability check. Existence checks and insertion are separate; concurrent creation/fallback collisions are not proven impossible. A name-only retry can create another project/key and is not idempotent.
Ordinary organization identity comes from the verified context. The existing organizations row is resolved by organization_id and its real legacy id is preserved; only absence causes a derived tenant id. Supplied tenantName can update that shared tenant row name, and mode updates its default_region_mode, so these effects are not confined to a new project display label.
The handler upserts a local consent_workspaces mirror for the verified workspace (existing status is not changed), counts active project rows and marks the new project default only when that count is zero. A new project id uses cp_ plus UUID. It inserts an active project with configJson:{}, no archivedAt and notificationUserId from the verified actor id, without a separate recipient-eligibility lookup here.
It seeds six purposes (essential,analytics,advertising,personalization,ai_training,support) using English-source translator labels and default decisions. Config reads order required first then purpose key. Empty vendor/domain/app storage can retain default-config fallbacks; a new project returns empty domains/apps and the default vendor list. The public SDK key is derived for the project/environment, not an OAuth/admin credential.
Tenant/mirror/project/purpose inserts, subsequent reads and config-cache/mapping writes are separately awaited and not an enclosing transaction/CAS shown here. Failure after some steps can leave partial records/configuration. Cache is attempted after read-back; missing KV can resolve without persistence, while a cache write error can fail after project creation. Do not equate a failed call with no creation or repeat it blindly.
The actual201 body is {project,config,source:d1}; project/config can be null after concurrent disappearance. Environment is stored project metadata (source defaults to staging only for staging host, otherwise production), not a switch of the credential’s actual platform environment or a deployment. Region mode selects configured behavior, not legal approval. Creation neither configures approved domains/apps nor proves a banner/integration is live.
Complete worked responses
Create the first active fixture project in an existing workspace
Complete synthetic/unrun HTTP201. Assumes original read_write/write grant for the existing fixture workspace, existing tenant row id org_record_fixture/name Fixture organization, free normalized explicit key and no other active project. The shown UUID/time are fictional. English source translation values are supplied by the fixture translator; six seed rows read back in SQL order, no prior cached config, empty vendor/domain/app tables use documented default fallbacks. No real source/runtime/translation/project operation was executed.
Input:
{
"name": "Fixture project",
"projectKey": "fixture-project",
"environment": "production",
"mode": "global"
}
Response:
{
"project": {
"id": "cp_00000000-0000-4000-8000-000000000001",
"key": "fixture-project",
"workspaceId": "ws_consent_fixture",
"name": "Fixture project",
"isDefault": true,
"tenantId": "org_record_fixture",
"authOrgId": "org_fixture",
"tenantName": "Fixture organization",
"environment": "production",
"mode": "global",
"status": "active",
"configJson": "{}",
"updatedAt": "2026-10-20T08:00:00.000Z",
"archivedAt": null,
"notificationUserId": "user_fixture_docs"
},
"config": {
"tenantId": "org_record_fixture",
"authOrgId": "org_fixture",
"projectId": "cp_00000000-0000-4000-8000-000000000001",
"workspaceId": "ws_consent_fixture",
"projectKey": "fixture-project",
"sdkKey": "pk_live_tc_17pdn1f11b86se07rgyil",
"mode": "global",
"updatedAt": "2026-10-20T08:00:00.000Z",
"cacheTtlSeconds": 300,
"staleWhileRevalidateSeconds": 86400,
"purposes": [
{
"id": "essential",
"label": "Essential operations",
"category": "essential",
"required": true,
"defaultDecision": "granted"
},
{
"id": "advertising",
"label": "Advertising and sale/share",
"category": "advertising",
"required": false,
"defaultDecision": "unknown"
},
{
"id": "ai_training",
"label": "AI training and improvement",
"category": "ai_training",
"required": false,
"defaultDecision": "unknown"
},
{
"id": "analytics",
"label": "Analytics",
"category": "analytics",
"required": false,
"defaultDecision": "unknown"
},
{
"id": "personalization",
"label": "Personalization",
"category": "personalization",
"required": false,
"defaultDecision": "unknown"
},
{
"id": "support",
"label": "Support workflows",
"category": "support",
"required": false,
"defaultDecision": "unknown"
}
],
"vendors": [
{
"id": "google",
"name": "Google",
"purposes": [
"analytics",
"advertising"
]
},
{
"id": "meta",
"name": "Meta",
"purposes": [
"advertising"
]
},
{
"id": "topolo",
"name": "Topolo services",
"purposes": [
"essential",
"support",
"ai_training"
]
},
{
"id": "stripe",
"name": "Stripe",
"purposes": [
"essential"
]
}
],
"domains": [],
"apps": [],
"copy": {
"title": "Privacy preferences",
"summary": "Choose optional data uses by purpose. Essential operations stay on, and you can change or withdraw optional choices later.",
"acceptAll": "Allow optional uses",
"rejectAll": "Reject non-essential",
"manage": "Manage choices",
"gpcApplied": "Global Privacy Control is enabled in this browser, so sale/share and restricted optional uses were opted out automatically."
}
},
"source": "d1"
}
Retain returned actual ids/key/default if executed and verify actual stored/integration state. The response does not create an Auth workspace or prove a deployed banner.
Explicit public key already owned
Complete synthetic/unrun HTTP409. Assumes prior Auth/workspace checks pass and normalized explicit key is already owned anywhere in Consent. No real global availability probe was performed; no foreign owner is disclosed by this fixture.
Input:
{
"name": "Fixture project",
"projectKey": "fixture-project"
}
Response:
{
"error": "project_key_not_available"
}
Do not claim an in-place configure/update occurred. Reconcile intended existing identity or separately approve a different real key; do not broaden context or guess foreign records.
Schema-valid empty intent rejected
Complete synthetic/unrun HTTP400. Assumes original organization/workspace/control checks pass but neither name nor usable projectKey is supplied. Input satisfies the declared schema; no real request was sent.
Input:
{}
Response:
{
"error": "project_name_required"
}
Prepare the intended nonblank name or valid normalized key and validate/plan again. Empty input is not an implicit workspace/default project creation.
Verify and recover
Compare returned actual project.id/key/workspaceId, name,status,isDefault,tenantId and intended environment/mode with original verified scope. Reconcile normalized/derived key and current workspace default from bounded projects.list; a missing row in its first200 is not proof that creation failed.
Inspect returned config and actual persistence with owner/current validated supported reads. Empty domains/apps mean no configured integration surface was demonstrated. Public SDK key, translated copy, vendor/purpose defaults and source:d1 are not evidence of live host coverage, legal review, complete storage health or notification eligibility.
After an uncertain response, reconcile actual project/key/default and tenant/mirror/purpose/cache state before considering another creation. Use the explicit known key/id if present; a name-only repeat can create another identity. Do not claim a whole project was rolled back because one later operation failed.
Creation has no exact rollback or input idempotency key. Preserve actual identity and reconcile partial state before any separate authorized cleanup. Project archival is a distinct admin status operation retaining records; the legacy workspaces.archive deletion has its own count-availability/source-contract qualification and must not be blindly used as cleanup. Do not delete workspace/organization data or write SQL/KV directly to undo a create, and do not overwrite shared tenant changes made by another owner.
Refusals
400 / project_name_required: Supply a reviewed nonblank name or usable normalized key; strict schema validity alone does not establish a meaningful create intent.
400 / workspace_required: Resolve the intended canonical workspace through original current policy. Do not pass project/org ids as workspace grants.
400 / auth_org_required: No owning organization resolved in this source branch; reconcile intended authorized context without impersonating another owner.
403 / organization_context_required: Preserve original intended context and refusal.
409 / project_key_not_available: Normalized explicit key is already taken globally; no update was performed by this pre-write branch.
503 / database_unavailable: Control database absent. Later SQL/cache failures require actual shared-runtime evidence and partial-state reconciliation, not an automatic create replay.
Shared Auth/workspace/permission checks precede this handler. Preserve actual SQL/cache/runtime request evidence and current record state after uncertainty; no observed live response or success is asserted by these fixtures.
Change log
- 2026-10-21: Added project-versus-workspace creation, globally normalized key selection, shared tenant/default effects, initial config/purpose behavior, nontransactional recovery and three complete source-derived unrun response fixtures.