Skip to main content

Portfolio Intake

Portfolio intake is the first governed workflow for artists texting portfolio work, flash, references, and ideas to a managed number. The product goal is personal for artists and strict for the platform: inbound media can be staged, classified, reviewed, and promoted into approved portfolio storage, but it cannot jump straight from a text message into public distribution.
Communications API credentials can be ready before SMS/MMS launch is ready. A2P approval remains required before treating SMS/MMS intake as production-ready.
Review-gated surface: this workflow can stage evidence and prepare review records, but it cannot publish, promote, or write approved portfolio data without TattooAPI policy checks and human approval.
Private workers cannot write directly to approved portfolio storage, mutate canonical runtime tables, activate public surfaces, change identity mappings, or publish inbound media. They return classification and review evidence to TattooAPI.

Flow

System Roles

Intake Contract

Inbound portfolio messages should normalize into a staging-only envelope:
Raw phone numbers, private bucket URLs, communications provider tokens, approved storage tokens, and worker API keys must not appear in committed examples, docs, reports, screenshots, or prompts.

Partner API Staging

Permanent and other approved private services use a WorkOS organization API key with portfolio_intake:stage. The current Production Permanent key has exactly artists:read and portfolio_intake:stage; artists:read is used to inspect existing artist identity evidence before staging.
TattooAPI returns a five-minute private R2 PUT URL and an awaiting_upload submission. Upload the exact media bytes using the returned Content-Type header. By default, the API key mapping must already include the requested studio or artist canonical ID; cross-tenant submissions return 403. An explicitly approved service mapping may carry the internal portfolio_intake:stage_cross_tenant capability for governed identity resolution and staging across approved tenants. This capability belongs to TattooAPI’s Convex actor mapping, not to the WorkOS key, and never grants approval or publication authority. While a submission remains awaiting_upload, legitimate idempotent replay returns the existing submission and a new signed URL for that submission’s persisted object key. TattooAPI never re-signs a PUT after completion. Reusing an idempotency key with a different semantic envelope fails closed instead of changing the staged pointer.

Complete the Upload

After the PUT succeeds, call the bodyless completion route with the same WorkOS organization API key and portfolio_intake:stage permission:
This route accepts X-API-Key only. Bearer-only requests and requests that mix Authorization with X-API-Key are rejected before submission lookup or storage verification. TattooAPI reads the private R2 object, checks its image signature and R2 MIME metadata, compares its actual byte count with the original submission, computes and persists a server-side SHA-256 digest, and requires an opaque ETag. Only image/jpeg, image/png, image/webp, and image/heic are accepted. image/heif is not supported. The first valid completion atomically transitions awaiting_upload to awaiting_review and returns action: "completed". A later replay returns HTTP 200 with action: "already_completed" from the persisted verification evidence. Missing objects, size or MIME mismatch, integrity conflict, or any other intake state leave the submission unadvanced. Completion is a staging transition only. Its governance envelope remains:
The submission remains review_required. A different operator identity with portfolio_intake:review must approve it through:
Approval is available only after upload completion has produced awaiting_review. It requires non-review rights and consent posture, verifies that the private R2 object exists, uploads the image to the approved Sanity media lake, and creates a private tattooPortfolioAsset draft. It does not activate public distribution or canonical Convex runtime promotion. The submitting service can poll its own bounded status through:
The response can move through awaiting_upload, awaiting_review, promotion_failed, and promoted_to_sanity. Even promoted_to_sanity does not make the asset public; public reads require a separate governed runtime and distribution promotion.

Identity Evidence, Not KYC

TattooAPI v0 identity evidence helps route portfolio media to review. It is not legal KYC and does not claim government identity verification. Do not label a record kyc_verified unless a future legal identity provider is explicitly added and approved.

Classification Contract

Workers return suggestions, not mutations:

Public Surface

The public developer surface should expose only reviewed projections and public-safe contracts:
  • approved portfolio reads
  • public artist/studio relationships
  • sanitized portfolio metadata
  • review and ownership posture fields
  • status-aware API documentation
It should not expose:
  • raw inbound SMS/MMS payloads
  • private conversation history
  • raw media staging URLs
  • direct approved-storage write paths
  • worker credentials or host setup secrets
  • automatic public publishing from inbound messages
Data posture legend: public_profile can appear in public projections, private_raw stays internal, sanitized_metric can feed signals, ontology_fact can enter the governed graph, and secret_reference never leaves host secrets or private runtime configuration.