Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* [🔐 Authentication](api-reference/authentication.md)
* [⚙️ Labs API](api-reference/labs-api/README.md)
* [Lab Management](api-reference/labs-api/lab-management.md)
* [Access Policies](api-reference/labs-api/access-policies.md)
* [Files](api-reference/labs-api/files.md)
* [Browse & Search](api-reference/labs-api/browse-and-search.md)
* [Legal Agreements](api-reference/labs-api/legal-agreements.md)
Expand Down
5 changes: 4 additions & 1 deletion api-reference/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ x-wallet-address: YOUR_WALLET_ADDRESS

Either way, the caller still has to be authorized for the target lab — a Service Token carries its own lab scope, and a Privy session is checked against the wallet's onchain role (LabNFT owner, authorized multisig signer, or an active role on `AccessResolver`). Supplying neither returns a `NO_AUTH` error naming both paths.

> **Permissionless labs still authenticate.** A lab owner can open specific capabilities — contributing files, editing, deleting, announcing — to callers who hold no role, either unconditionally, until a deadline, or subject to an onchain condition. That removes the *membership* requirement, never the *identity* one: the caller still presents an API Key plus a Privy session or a Service Token, and any wallet can self-issue a Service Token via the [wallet-signature flow](labs-api/service-tokens.md#obtaining-tokens). See [Access Policies](labs-api/access-policies.md).

**Mutations accepting either path:**

- `createLab` - Create a lab (data room) for an onchain lab (OCL) · 💳 also available pay-per-call via [x402 Gateway](x402-gateway.md)
Expand All @@ -95,10 +97,11 @@ Either way, the caller still has to be authorized for the target lab — a Servi
- `updateFileMetadata` - Update file metadata
- `deleteDataRoomFile` - Delete a file
- `createAnnouncement` - Create an announcement · 💳 also available pay-per-call via [x402 Gateway](x402-gateway.md)
- `updateLabAccessPolicy` - Set a lab's contribution-access policy (OCL admin only)
- `updateLabNftMetadata` - Update LabNFT display metadata (OCL admin only)
- `generateLabImageUploadUrl` - Get a presigned URL to upload a LabNFT image (OCL admin only)
- `signLegalAgreement` - Record acceptance of a legal agreement
- `generateDataEncryptionKey` - Generate a standalone data encryption key · 💳 also available pay-per-call via [x402 Gateway](x402-gateway.md)
- `generateDataEncryptionKey` - Generate a data encryption key for a lab file, bound to its access conditions · 💳 also available pay-per-call via [x402 Gateway](x402-gateway.md)
- `decryptDataKey` - Decrypt a file's data key for an authorized caller · 💳 also available pay-per-call via [x402 Gateway](x402-gateway.md)

**Service-Token-only mutations** — these manage token lifecycle and reject Privy sessions:
Expand Down
68 changes: 68 additions & 0 deletions api-reference/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,80 @@ All Molecule APIs (Labs, Tokenization, and IPNFT (Deprecated) — they share one

## Labs API

### Lab access policies — permissionless & condition-gated labs

Labs can now open individual data-room capabilities beyond their onchain members. This is **purely additive**: a lab created without a policy behaves exactly as before, including its error responses, and a role grant always wins over a policy, so no existing integration changes.

#### New mutation

| Mutation | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `updateLabAccessPolicy` | Set a lab's contribution-access policy — open it up, gate it back down (`preset: GATED`), or configure per-capability rules. Owner-only |

#### New input field

`CreateLabInput` gained an optional `accessPolicy: LabAccessPolicyInput`, which creates the lab already open (or condition-gated). Omitting it keeps the default role-gated lab. Passing it restricts `createLab` to the lab owner.

#### New output field

`Lab.accessPolicy` and `LabRef.accessPolicy` expose the stored policy as `LabAccessPolicy` — public, since policies gate access rather than being secrets. Labs without a stored policy return the synthesized `GATED` default. Render from `capabilities`; `preset` is a provenance hint and is `null` for custom configurations.

#### New enums and inputs

`LabAccessPreset` (`GATED` | `OPEN`), `LabCapability` (`ADD_FILES` | `MODIFY_FILES` | `DELETE_FILES` | `CREATE_ANNOUNCEMENTS` | `DECRYPT_FILES`), `LabPolicyRuleKind` (`ROLES` | `ANYONE` | `CONDITIONS`), `LabAccessPolicyInput`, `LabCapabilityPolicyInput`, and the output types `LabAccessPolicy` / `LabCapabilityPolicy`.

#### What to watch for when integrating

* Writes to an open lab still require authentication — an API Key plus a Privy session or a Service Token. Permissionless is not unauthenticated.
* On a policy-granted write, `changeBy` is pinned to the authenticated caller; a spoofed value is ignored.
* `DECRYPT_FILES` defaults to viewer-or-above (not contributor-or-above like the write capabilities) and is **not** included in the `OPEN` preset — an open lab's encrypted files stay member-readable-only until the owner adds an explicit rule.
* Denials add second-level causes on `details.reason`: `CAPABILITY_DENIED`, `INVALID_ACCESS_POLICY`, `POLICY_CHECK_UNAVAILABLE` (retryable), `LAB_ACCESS_CHECK_FAILED`. No new top-level `error.code` values were introduced.

Full reference: [Access Policies](labs-api/access-policies.md).

---

### Breaking: `generateDataEncryptionKey` now requires `oclId` and `accessControlConditions`

Minting a data encryption key is now lab-scoped and bound to the file's condition array up front, closing a gap where an encrypted file's DEK could be re-published under different access conditions after the fact.

* `generateDataEncryptionKey` gained two **required** arguments: `oclId: String!` and `accessControlConditions: String!`. Calls with no arguments now fail `VALIDATION_FAILED`.
* The returned `encryptedDek` carries a `v1:` bound-marker prefix ahead of the base64 ciphertext — pass it through to `finishCreateOrUpdateFile` **verbatim**. A new `dekContextVersion` field (`"v1"`, or `null` for legacy pre-cutover DEKs) surfaces the binding state on both the mutation result and `EncryptionMetadata`.
* The DEK is cryptographically bound (KMS `EncryptionContext`) to `{oclId, sha256(canonicalized accessControlConditions)}`. Passing a different condition array to `finishCreateOrUpdateFile` than the one used to generate the key produces a permanently undecryptable file.
* Minting is gated by the lab's `ADD_FILES` capability (falling back to `MODIFY_FILES`) — the same rule an open lab's contributions follow.

Full reference: [Data Encryption Keys](labs-api/files.md#data-encryption-keys) and [DEK Binding](../technical-deep-dive/data/data-privacy-and-access.md#dek-binding).

---

### New capability: `DECRYPT_FILES`, and a lab-level gate on `decryptDataKey`

`decryptDataKey` called with an `oclId` is now gated by the new `DECRYPT_FILES` access-policy capability *before* the file's own `accessControlConditions` are evaluated. The default behavior is unchanged (viewer-or-above membership, for both a Privy session and a service token) — what's new is that a lab owner can widen it via `updateLabAccessPolicy`, so a non-member who contributed to an open lab can read their own encrypted submission back. The `tokenUri`-only path (IPFS agreement documents, no `oclId`) is unaffected and keeps its existing paid-access carve-out, but now also goes through the same x402 `allowedMutations` scope check as every other gated mutation (previously bypassed on that branch).

Full reference: [Decrypt Authorization](../technical-deep-dive/data/data-privacy-and-access.md#decrypt-authorization).

---

### Breaking: service-token wallet sign-in moves to EIP-712 + single-use nonces

`getServiceSignInMessage` now returns a stateful, single-use challenge instead of a deterministic string, closing a replay gap where one captured signature could mint tokens indefinitely.

* `message` is now JSON-serialized **EIP-712 typed data** — sign it with `eth_signTypedData_v4` (viem `signTypedData`). Signatures over the old plain-text message (`personal_sign`) are **no longer accepted**.
* The query response gained `nonce`, `issuedAt`, and `expiresAt`. The nonce is single-use, expires after roughly 10 minutes, and must be passed to `generateServiceToken`'s new required-with-signature `nonce` argument. A reused or expired nonce fails `UNAUTHENTICATED` / `details.reason: "INVALID_NONCE"`.
* `expiresIn` on both `generateServiceToken` and `extendServiceToken` is now clamped to **\[1 hour, 2 years]**; out-of-range or malformed values fail `VALIDATION_FAILED` instead of a masked internal error.

Full reference: [Obtaining Tokens](labs-api/service-tokens.md#obtaining-tokens).

---

### GraphQL introspection disabled and query depth capped in production

The production endpoint (shared by all Molecule APIs — see [API Overview](README.md)) no longer serves `__schema` / `__type` introspection queries: they now return a validation error. `__typename` still resolves. Selection-set depth is also capped at 10 in production, with scalar leaves counted as a level (`{ root { child { name } } }` is depth 3). A query beyond that limit fails at execution time with `errorType: "QueryDepthLimitReached"` and partial data — a plain GraphQL error, not the catalogued error shape used elsewhere, so handle both.

**Migration:** If your codegen or tooling discovers the schema by introspecting the production endpoint, that now fails — request a current copy of the schema from the Molecule team (see [Getting Support](README.md)) rather than introspecting production. If you see `QueryDepthLimitReached`, flatten the query to 10 levels of nesting or fewer; this limit was not previously enforced.

---

### `*V2` operations and pre-OCL naming removed

The legacy `*V2` operations and the pre-OCL naming have been **removed**. The current API is `oclId`-based. If you are migrating from an older integration, use the current names below.
Expand Down
4 changes: 3 additions & 1 deletion api-reference/labs-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ The Labs API allows developers to interact with Molecule Labs datarooms without

The Labs API uses consumer-credential authentication for reads and an additional Service Token for writes. Full details — public queries vs. protected mutations, obtaining and using credentials — are on the [Authentication](../authentication.md) page.

See also the functional sections: [Lab Management](lab-management.md), [Files](files.md), [Browse & Search](browse-and-search.md), [Legal Agreements](legal-agreements.md), and [Service Tokens](service-tokens.md).
See also the functional sections: [Lab Management](lab-management.md), [Access Policies](access-policies.md), [Files](files.md), [Browse & Search](browse-and-search.md), [Legal Agreements](legal-agreements.md), and [Service Tokens](service-tokens.md).

> **Permissionless Labs.** By default a Lab is role-gated: only its owner and contributors can write. A Lab owner can additionally open specific capabilities — file contributions, edits, deletions, announcements — to any authenticated caller, to a deadline, or to wallets satisfying an onchain condition. Note that permissionless does not mean unauthenticated; see [Access Policies](access-policies.md).

---

Expand Down
Loading