Skip to main content

Connected applications

A registered partner application calls Tessera keylessly. It authenticates with a Google-signed OIDC ID token minted by its own workload identity, and it acts only on the organizations and projects it created. This page is the integrator's reference (WP-A9).

Authentication​

  • Mint an ID token for the environment's exact audience (no trailing slash, no /v1), with format=full.
  • Send it as Authorization: Bearer <id token> to the API host or the ingest host. Both accept the same audience.
  • Tessera pins the token on the service account's email AND unique id (sub), requires email_verified: true (the boolean), and refuses tokens older than one hour.
  • A registered application never holds an API key.

Scoping​

  • Project operations need X-Tessera-Project and X-Tessera-Environment.
  • Add X-Tessera-Project-Ref and X-Tessera-Org-Ref (your external_ref values) for defence in depth. A mismatch is the same 404 as a project that does not exist.
  • An environment outside your registry entry is 403 environment_not_allowed.
  • Human context goes in X-Tessera-On-Behalf-Of: an opaque reference matching ^[A-Za-z0-9._:-]{1,128}$, never an email or a name. It is recorded only as details.unverified_annotation on audit events. X-Tessera-Actor-Label is refused.

Provisioning​

  • Create orgs and projects with POST /application/orgs and POST /application/orgs/{org_id}/projects.
  • The external_ref is the idempotency key. A repeat with the same body returns 200; a different body is 409 with resource_id.
  • Refs are retired on delete and never reused (409 external_ref_retired). An Idempotency-Key header is ignored on these creates. A delete that Tessera operations discards as forged (see "While your application is disabled" below) retires nothing: the org or project returns with its ref, and a create with that ref is again a repeat of it.
  • Managed orgs start with zero members, on your configured plan.
  • GET /credential/owned lists everything you created in one place: every org, a page at a time (cursor, limit, ordered by org_id), each with every project you created in it. Every status is listed, deleting and deleted included. Each org and project reads exactly as GET /application/orgs/{org_id} and GET /application/projects/{project_id} return it. Sessions and API keys get 403.

Members​

People reach a managed org only when your user opts in and you provision them. Tessera sends no email or invitation.

  1. Add the person with POST /application/orgs/{org_id}/members ({email, role}, role admin, member or viewer; never owner). A new member is always pending, and the answer never says whether the email already has a Tessera account. The normalized email (trimmed, lowercased; ASCII or IDNA only) is the idempotency key: a repeat with the same role returns 200 with the member as it is, and another role is 409 with resource_id.
  2. Mint a claim code with POST /application/orgs/{org_id}/members/{member_id}:claim-code. It is returned once, valid for at most 15 minutes, single use, and replaced by the next code you mint. Show it to the person in your own product.
  3. The person signs in to the Tessera console with the same, verified email (email and password, or Google; not an SSO tenant) and enters the code on the console's claim page. Link the person to <console>/claim#code=<claim_code> (the console URL is in the per-environment constants): the code travels in the URL fragment, which the browser never sends to a server, and the page removes it from the address bar; the person still confirms with one click. Only then does the member show active. Your product never handles the person's Tessera sign-in or session: it shows the code and sends the person to the console.

A pending member expires 72 hours after the add; add them again for a new member_id. You list (GET .../members), re-role (PATCH .../members/{member_id}) and remove (DELETE .../members/{member_id}) only people you provisioned; people added any other way are only counted, in unmanaged.members. A role change or removal takes effect on the person's next request. Removal, or a role below admin, revokes every API key that person created in the org. A removed member's row keeps no email address; an expired one is scrubbed by a scheduled pass, normally within minutes of its expiry (at most 25 a pass every 5 minutes across the platform, oldest first). The org's audit log is immutable: it keeps the redeeming person's email as the display name of the actor that redeemed.

Once a person redeems, the org's audit log (which you read with GET /application/orgs/{org_id}/audit-events) names them by their internal Tessera user id as the actor of application_member.materialize and role.assign.

Limits: members_per_org live members, and members_churn_per_day adds plus removals in a rolling 24 hours (429 application_quota with quota; the churn refusal also carries Retry-After). Adding or minting a code in an org that is not active is 400 failed_precondition (org_not_active); a code for a member who already redeemed is 400 failed_precondition (member_already_active). Removal works in an org of any status, but it counts toward the churn limit.

Removals count toward the churn limit

Once an org has used its members_churn_per_day (adds and removals together), a removal is refused with 429 until the oldest counted add or removal leaves the 24-hour window, so for up to 24 hours. During that time the person keeps their access. Keep headroom for urgent removals: don't spend the whole budget on adds. Removal is the only per-person cut-off. If an urgent removal is blocked, contact Tessera operations: they can set your application's kill switch, which stops every call of your application and every claim-code redemption at once, but does not end that person's existing access by itself.

The person's redemption is refused:

  • while your application is disabled (either kill-switch layer): 403 permission_denied (reason: application_disabled), or 503 unavailable with Retry-After when Tessera cannot read the kill-switch state;
  • while the org is suspended or being deleted: 400 failed_precondition (org_not_active);
  • for a code minted before your application's tokens were revoked as of an instant: the uniform 404.

A refusal does not use up the code; it stays valid until it expires.

Lifecycle​

  • Rename with PATCH /application/orgs/{org_id} or PATCH /application/projects/{project_id} ({name}). The onboarding mode is fixed at creation.
  • Suspend with POST /application/orgs/{org_id}:suspend or POST /application/projects/{project_id}:suspend, and lift it with :resume. The data is kept.
    • What it refuses. Within 5 seconds, every data-plane request to the project is refused with 403 org_suspended or project_suspended, from your application, its people and any API key. Work started through the API is refused with it.
    • What it does not do. It freezes the project data plane only. Org-level reads continue for the org's people: the org, its project list, its members and, for admins, its audit log and model inventory. It removes nobody and revokes no key; to cut a person off, remove them. Nobody can redeem a claim code into a suspended org. Scheduled work (syncs, refreshes, scheduled exports, webhooks, alert sweeps) keeps running until slice 6b skips it.
    • You keep the provisioning operations and the event-feed reads.
  • Delete with DELETE /application/projects/{project_id}: the project reads deleting with its purge_after (a 7-day grace), its API keys stop at once, and the purge then leaves a deleted tombstone. You cannot restore it: suspend first and keep your own grace window while suspended. (The one exception is a forged delete Tessera operations discards while your application is disabled, below.)
  • DELETE /application/orgs/{org_id} deletes every project you created in the org the same way, and the org's people lose access at once. Once every project is purged the org reads deleted: its people, SSO, domains, SCIM tokens and invites are removed, by a scheduled pass every 5 minutes after the last purge.
    • It is refused with 400 org_has_unowned_projects while the org holds a project you did not create, which only an org created before the managed-org guard can. You cannot see or delete that project; ask the org's owner to delete it (or Tessera operations), then retry.
  • Lifecycle changes on a deleting or deleted object are 400 org_not_active or project_not_active. A repeat of a suspend, resume or delete answers 200 with the object as it is.
  • While Tessera operations has your application disabled with its database kill switch, everything destructive it requested waits, and nothing is canceled: project purges, org finalization, model-file and dataset-file purges (including retention you shortened) and entity erasures. It resumes once the switch is cleared. (The registry's enabled: false stops your calls but pauses nothing.) While the switch is set, nobody else changes the retention of the projects your application created: the people you provisioned keep their sessions, but putModelFileRetention and putDatasetFileRetention answer them (and any API key) 403 permission_denied (reason: managed_org). One exception: the event feed and event exports act on an entity erasure from the moment deleteEntity accepts it, and never read the switch. Within seconds the entity's feed items are redacted in storage, an entity_deleted item is appended, and every existing export part holding the entity is rewritten without it. This also happens while the switch is set, whenever the feed or the export maintenance was still behind when the switch was set. These are copies derived from the event store; the stored events, serving and training keep the data until the erasure itself runs.
  • Before clearing it, Tessera operations reviews every paused item with you, and you confirm, through your out-of-band contact, which requests were yours. For the ones you confirm forged, while the switch stays set:
    • A project or org delete whose purge has not started is discarded: the project returns to active (or suspended) and its purge reads canceled, the org's delete is withdrawn, and their refs stay theirs. API keys, SCIM tokens and invites the delete revoked stay revoked.
    • An explicit model-file or dataset-file purge request is withdrawn, as cancelModelFilePurge and cancelDatasetFilePurge withdraw one: the files return to the project's retention, which still applies.
    • A retention setting your application, or a person it provisioned, changed last is restored (audited as an operator restore): by default to the value it had before your application's changes (the value before the oldest of its unbroken run of changes, as the audit recorded them), or to another value you confirm (1 to 365 days, or off). While the switch is set, Tessera can correct its own restore. off means no retention: old dataset builds and archived models keep their files until someone requests their purge, and an old build keeps its snapshot, so an erasure waiting on it cannot complete. A dataset-file value that would leave a build your genuine erasures wait on unscheduled, or starting its purge after their 42-day bound, is refused.
    • A model your application, or a person it provisioned, archived is listed with who archived it: its retention purge waits until you confirm the archive (or it is treated like a forged item that cannot be withdrawn, below).
    • A purge that has already started, or a forged archive, cannot be withdrawn. For such an item, you (as controller) and Tessera decide together whether the application stays disabled for good or the loss is accepted and the rest resumes. Kept disabled for good, nothing destructive it requested runs again, unless released: no project purge, org finalization, model-file or dataset-file purge of the projects it created (by request or by retention), and no erasure it requested that nobody released. Your genuine erasures still complete, once Tessera operations releases them and, while the switch stays set, what they wait on: the dataset builds whose snapshots they need, or, in a project being deleted, that project's purge (its deletion then runs, so the erasure completes with it). An erasure in a project whose delete you confirm forged completes once that delete is discarded, or, if its purge already started, once its loss is accepted and the purge released. An accepted loss takes two people at Tessera, each from an operator Build they submit themselves: one approves, the other accepts within a day, and either can revoke the approval before the switch is cleared (a purge released under it stops at its next step; an erasure Tessera already released, or a purge step already running, is not undone). Tessera's tooling checks that two different people submitted the two Builds and records both: the separation is checked and auditable, not enforced against an operator who alters the Build itself (a preventive control is planned before production). Everything else you confirm genuine runs once the switch is cleared.
  • As the controller, keep your data subjects' erasure deadlines in view: they keep running while your application is disabled, and meanwhile no erasure can be requested in your managed orgs at all (deleteEntity is yours alone there). Tessera operations can release specific paused erasures while the switch stays set (and the dataset builds or project purges a released erasure waits on, so it completes), and can discard the requests you confirm were forged. To tell them apart, check each paused deletion_id against the ids your own system received back from deleteEntity: any id you received is genuine for you, even if a forged request created the record first (a repeated request for the same entity is answered with the existing record). A discarded request never runs and no longer appears in listEntityDeletions or getEntityDeletion. A request whose erasure has already started cannot be discarded.
  • What a discarded request's acceptance did stays, for the life of the project: the entity's earlier feed items stay redacted (their payload was removed at acceptance); every later event of the entity reaches your event feed as event_redacted and is left out of every later event export (serving and training still use those events); those redactions name the discarded deletion id, which getEntityDeletion answers 404; every later export's deletions object lists that id with completed_at null; and the entity's lines are gone for good from every export part that held them at acceptance (each part was rewritten without them and is downloadable again, with the other entities). The discard removes the raw id from the request's own entity_deleted feed item, which then reads like a completed deletion's (entity.id null), although no entity_deletion_completed item follows. A later deleteEntity of the same entity still starts a new erasure and erases it; when it completes, no feed item names the entity's raw id.

Exports​

An application can export only to Tessera-managed storage:

  • projection_2d exports;
  • state snapshots to the default gcs destination, sent with {"destination": {"kind": "gcs"}} and no uri. The files land under the managed export prefix listed below and are served through signed URLs.

Any other destination is outbound delivery and returns 403 application_operation_not_allowed before anything is created:

  • a gcs destination with a uri, including a different prefix in the default bucket;
  • bigquery, s3 or snowflake.

In a managed org, sessions and API keys cannot export at all (403 managed_org).

Leakage policy​

The leakage policy is project-wide: one version governs both environments. PUT /leakage-policy replaces it, and an application may only tighten it:

  • keep every deny pattern and deny attribute of the current version (read it with GET /leakage-policy), and add your own;
  • never turn identifier_value_scan or label_temporal_gate off.

Anything else returns 403 permission_denied with the reason application_policy_relaxation and the relaxed fields, and writes nothing. Deny patterns Tessera registers for your label names count as current patterns, so include them too.

File retention​

Model file and dataset build retention (PUT /model-file-retention, PUT /dataset-file-retention) are project-wide too. An API key or console session acting in the test environment may only keep files longer, but an application may turn retention on or shorten it in the projects it owns, also when it acts in test: turning both on (for example at 6 days) is how the partner flow bounds the time an erasure takes to complete.

Approvals and attribution​

Contract approvals, onboarding approvals, domain-pack installs, alert rules and every other write you make are recorded as application:<application_id>. Send the reference to your own approval record in X-Tessera-On-Behalf-Of. You keep the human approval evidence. See Governance evidence.

Per-environment constants​

ConstantOur hosted deployment (test environment)
API and ingest audiencehttps://api.staging.tessera.zone
Console (the claim page is /claim)https://console.staging.tessera.zone
Upload object path (signed upload URL host storage.googleapis.com)gs://tessera-<env>-data/projects/<project_id>/uploads/<upload_id>/<filename>
State-snapshot and projection_2d exports without a urigs://tessera-<env>-exports/projects/<project_id>/exports/<export_id>/, served through 7-day signed URLs
Implicit ingest source for POST /eventsapp:<application_id>, one per project and environment (the app: source-name namespace is reserved: no caller can create or rename a source into it)
Safe completeUpload object sizePending (HD-114): measured on our hosted deployment with the first release of this slice (the whole object is hashed within the load balancer's 30 s window)
Connector egress IPsNot applicable until the connector destination guard lands; applications are push-only

Limits and errors​

StatusCodedetails[0]
400invalid_argumentfield_violation, including the scoping headers and X-Tessera-On-Behalf-Of
400failed_preconditionreason: org_not_active (an add, claim code or lifecycle change on an org that is not active), project_not_active (a lifecycle change on a deleting or deleted project), org_has_unowned_projects (an org delete while the org holds a project you did not create) or member_already_active (a claim code for a member who already redeemed), plus each operation's documented reasons
401unauthenticatednone
403permission_deniedreason: application_disabled, application_operation_not_allowed, application_source_kind_not_allowed, application_policy_relaxation, environment_not_allowed, project_suspended, org_suspended, managed_org or purpose_not_allowed
404not_foundnone: the uniform body for unknown, foreign and human-created objects
409already_existsreason: external_ref_retired or idempotency_key_reused, plus resource_id
429resource_exhaustedrate_limit with bucket and class (always with Retry-After), or reason: application_quota with quota (with Retry-After for the per-day quotas)
503unavailablenone, with Retry-After

Your per-class limits (ingest, read, admin) are reported by GET /credential.