Skip to content

docs(documents): define supported file types and document rejection responses - #861

Merged
pengying merged 2 commits into
mainfrom
08-24-document-file-types
Aug 26, 2026
Merged

docs(documents): define supported file types and document rejection responses#861
pengying merged 2 commits into
mainfrom
08-24-document-file-types

Conversation

@ls-bolt

@ls-bolt ls-bolt Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

The Grid docs told integrators only "Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB." A partner hit a 422 DOCUMENT_REJECTED / dataNotReadable on a passport upload, asked what the minimum image requirements are, and we had nothing published to point them at. This defines the file contract on POST /documents and PUT /documents/{documentId}.

Changes

File types. Named as the exact content types the API matches on — application/pdf, image/jpeg, image/png — and stated that matching is on the multipart part's Content-Type, not the filename extension. That distinction is load-bearing: an integrator sending a HEIC as passport.jpg, or any client that omits a per-part type, gets a 400 with no obvious cause. image/heic, image/webp, and image/tiff are called out as rejected, since those are what a phone camera roll actually produces.

Photo requirements. The endpoint descriptions now list what a document image must satisfy to pass the provider's upload screen: whole document in frame with all four corners, in focus and free of glare, in color, an actual photo or scan rather than a screen capture or an edited image, and unexpired. Each corresponds to a rejection label the provider can return.

422 DOCUMENT_REJECTED. Both operations already return this, and neither documented it. Adds Error422 with the precheckErrors array and the meaning of the common labels. precheckErrors was verified against the implementation, not inferred — see Verification below. The two rejection paths are also distinguished, because they need different fixes: a 422 on upload creates no document, so it is retried with POST, while a document rejected later during review has an ID and should be replaced with PUT so the rejected file is marked inactive.

Verification

  • make lint passes: 0 errors, exit 0. Warning and info counts match main exactly (148 / 518), apart from 3 new schema-properties-have-examples infos on Error422 — the same three every existing Error4xx schema already emits.
  • make build rebundles cleanly and is idempotent; openapi.yaml and mintlify/openapi.yaml are identical.
  • Every published claim was checked against the implementation in sparkcore rather than against the existing docs.
  • precheckErrors is really returned, traced end to end in webdev: both grid/api_handlers/upload_document.py and replace_document.py raise GridException(error_code=ErrorCode.DOCUMENT_REJECTED, details={"precheckErrors": ex.errors}); ErrorCode.DOCUMENT_REJECTED is defined with http_status_code=422; the @grid_api decorator that wraps both routes serializes it via jsonify(ex.to_dict()) with ex.http_status_code; and GridException.to_dict() does body.update(self.details), which places precheckErrors at the top level of the response body rather than nested under details — matching the schema as written. The dashboard client reads it from that top-level position (gridApiErrorParsing.ts). The four documented labels (dataNotReadable, screenshot, grayscale, documentExpired) all appear in the client's provider-label map in kyb/kybValidation.ts.

One correction this surfaced

The old "max 10 MB" is accurate as a file limit but the enforcement is a whole-request cap set slightly above it, so the file limit is not checked directly. The docs now state the 10 MB file limit, which is the number an integrator should build against.

Worth flagging separately, as code changes rather than doc ones: when the provider rejects an oversized file, Grid passes through a message telling the caller to reduce it to under 5 MB, which contradicts the 10 MB we publish. Any file between roughly 5 and 10 MB can pass Grid's own checks and then fail upstream with that message. I have not changed either number here — reconciling them is a behavior decision, not a docs one.

Test plan

  • make lint passes with 0 errors
  • make build produces no diff on re-run
  • File types, size limit, and error codes verified against the sparkcore handlers
  • Spot-check Mintlify rendering of Documents → Upload a document

Requested by @pengying

@mintlify

mintlify Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Grid 🟢 Ready View Preview Aug 24, 2026, 7:45 PM

@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Aug 25, 2026 11:39pm
grid-wallet-demo Ignored Ignored Preview Aug 25, 2026 11:39pm

Request Review

@ls-bolt ls-bolt Bot added the bolt label Aug 24, 2026

Copy link
Copy Markdown

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

✱ Stainless preview builds for grid

This PR will update the grid SDKs with the following commit messages.

cli

docs(api): update parameter descriptions for documents replace and upload methods

go

docs(api): expand file requirements documentation in document upload/replace

kotlin

docs(api): update documents upload/replace method documentation

openapi

feat(api): add 422 error responses to document upload/replace

php

docs(api): expand file requirements in documents upload/replace methods

python

docs(api): enhance documentation for documents upload and replace methods

ruby

docs(api): expand file upload requirements in documents resource

typescript

docs: expand documents upload and replace method descriptions
⚠️ grid-openapi studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️

⚠️ grid-ruby studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️build ⏭️lint ⏭️test ⏭️

⚠️ grid-go studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️build ⏭️lint ⏭️test ⏭️

go get github.com/stainless-sdks/grid-go@80011836706bf6fc1e437d19ac7415f62b2eb472
⚠️ grid-kotlin studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️build ⏭️lint ⏭️test ⏭️

⚠️ grid-typescript studio · conflict

Your SDK build had at least one warning diagnostic.

⚠️ grid-python studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️build ⏭️lint ⏭️test ⏭️

⚠️ grid-php studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️lint ⏭️test ⏭️

⚠️ grid-cli studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️build ⏭️lint ⏭️test ⏭️


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-08-26 16:29:52 UTC

message:
type: string
description: Error message
precheckErrors:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure precheckErrors is returned? Can you verify from the webdev repo?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified — it is returned, and the trace turned up one detail worth having in the schema.

Both handlers raise it (sparkcore/grid/api_handlers/):

raise GridException(
    reason=...,
    error_code=ErrorCode.DOCUMENT_REJECTED,
    details={"precheckErrors": ex.errors},
)

upload_document.py:167 on SumsubDocumentRejectedError, and replace_document.py:133 on the same. ErrorCode.DOCUMENT_REJECTED is declared http_status_code=422 in grid/exceptions.py:52, so the 422 pairing is right.

The path to the wire: both routes are wrapped in @grid_api, whose handler at grid_api.py:695-707 does jsonify(ex.to_dict()) with ex.http_status_code. And GridException.to_dict() does:

if self.details:
    body.update(self.details)

That's update, not body["details"] = ... — so precheckErrors is spread at the top level of the error body, not nested under details. That matches this schema as written, which is what I wanted to confirm rather than assume.

Independent confirmation from the consumer side: the dashboard reads it from the top level first in js/apps/private/site/src/uma-nage/gridApiErrorParsing.ts:52, with the nested positions only as fallbacks.

I also checked the four labels I documented rather than leaving them plausible-looking: dataNotReadable, screenshot, grayscale, and documentExpired all appear in the provider-label map in uma-nage/kyb/kybValidation.ts.

One thing I removed while here: the sentence claiming Grid "cannot break one down further" — that's the same claim you asked to drop, so it went with the rest.


🤖 voltaic-hadron-3(#3) | Feedback

Comment thread openapi/paths/documents/documents.yaml Outdated
- be unexpired

Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB.
Grid runs no image analysis of its own and sets no resolution floor. The

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this grid runs no image analysis text

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed — and per your follow-up in Slack, everywhere it appeared, not just here:

  • this description (POST /documents)
  • PUT /documents/{documentId}, which carried the same paragraph
  • BaseDocumentRequest.yaml, the one you first pointed at
  • Error422.yaml, where precheckErrors restated the claim ("Grid does not compute these labels and cannot break one down further")

grep for "image analysis" and "resolution floor" across openapi/ and mintlify/ now returns nothing. The concrete photo requirements stay, since those are what an integrator can act on.


🤖 voltaic-hadron-3(#3) | Feedback

$ref: ../../components/schemas/errors/Error404.yaml
'422':
description: >-
The verification provider rejected the file. `precheckErrors` names

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify the precheckErrors is actually returned

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified — it is returned on this endpoint too. replace_document.py:133 raises GridException(error_code=ErrorCode.DOCUMENT_REJECTED, details={"precheckErrors": e.errors}) on SumsubDocumentRejectedError, the same shape as the upload path.

Worth noting the two differ in one respect that this description already reflects: replace rolls back the metadata mutation before raising, so a rejected PUT leaves the stored document on its previous file and metadata.

Full trace through to_dict() and the @grid_api wrapper — including the finding that precheckErrors lands at the top level of the body rather than under details — is in the Error422.yaml thread, so I won't duplicate it here.


🤖 voltaic-hadron-3(#3) | Feedback

@ls-bolt

ls-bolt Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Revision log — updated in place, latest first.

Revision 1

  • Removed the "Grid runs no image analysis" text everywhere it appeared, not just in BaseDocumentRequest.yaml: also both endpoint descriptions (POST /documents, PUT /documents/{documentId}) and the restatement of the same claim on precheckErrors in Error422.yaml.
  • Verified precheckErrors really is returned, by tracing it end to end in webdev rather than inferring it. Details in the thread and in the updated PR description — the notable part is that it lands at the top level of the error body, not nested under details, which is what the schema says.
  • Rebundled; make lint clean (0 errors, 148 warnings — unchanged from main).

pengying and others added 2 commits August 25, 2026 23:38
…esponses

Publish the file contract for POST /documents and PUT /documents/{documentId}:
the three accepted content types, that matching is on the multipart
Content-Type rather than the file extension, the 10 MB request body cap, and
the photo requirements a document has to meet to pass the provider's upload
screen.

Add the 422 DOCUMENT_REJECTED response both operations already return, with
the precheckErrors array that names each problem found.
Remove the paragraph from the BaseDocumentRequest file field and from
both document endpoint descriptions, along with the restatement on
precheckErrors, and rebundle.

Co-Authored-By: pengying <pengying@users.noreply.github.com>
@pengying
pengying merged commit 80ed2f3 into main Aug 26, 2026
10 checks passed
@pengying
pengying deleted the 08-24-document-file-types branch August 26, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants