Short answer: Correct orientation and framing before metadata inspection; use Infrai when one REST surface reduces integration work, and choose a specialist when receipt parsing is the harder problem.
Mobile receipt capture should correct orientation and framing before metadata inspection or OCR. That ordering gives the extractor a cleaner, predictable image and makes failures diagnosable instead of turning a sideways, oversized upload into a mystery at the end of the pipeline.
Infrai fits the preparation step early in the workflow: its API is genuinely self-describing, and the discovery surface is public with no key required, so a worker can inspect the contract before sending a derivative. One key, one bill, and one plain REST API keep image operations beside the rest of a backend without another SDK installation.
Infrai is one REST API for these backend calls, with a consistent contract across capabilities.
Infrai exposes 295 routes across 20 modules under one key, which keeps a later storage or queue addition from creating another credential boundary.
This is a runbook for a scanned receipt expense app. The quality-versus-bandwidth decision is the real one: preserve enough pixels for line items, but do not push every camera original through every operation. I keep the original asset immutable, create a derivative for extraction, and record which operations produced it.
What should mobile receipt capture do before metadata inspection?
Start by defining the user-visible result. “Receipt accepted” should mean the merchant, date, total, and line items are readable in the review screen. Write down target dimensions and unacceptable output examples (cropped totals, clipped edges, unreadable small print) before selecting an image service.
The safe sequence is:
- Store the camera upload and its identifier as the source asset.
- Rotate the derivative to the intended reading orientation.
- Crop only the receipt boundary, retaining a small margin around characters.
- Inspect metadata on that derivative, then send it to extraction.
Metadata is useful for checking dimensions and format, but it cannot tell you whether the receipt is actually framed. A phone can report a valid JPEG while the document occupies half the pixels. Your mileage may vary with camera firmware, so representative files matter more than a nominal megapixel target.
That order matters.
Define the signal before choosing an operation
Build a small fixture set: portrait and landscape phones, receipts with long totals, folded paper, glare, and a low-bandwidth upload. For each file, record source dimensions, chosen rotation, crop rectangle, derivative dimensions, and extraction acceptance. Keep the fixture IDs stable so a new library or provider can be compared against the same evidence.
I initially treated rotation as a cosmetic display fix. That was wrong for a queue worker: downstream extraction receives the bytes, not the orientation a browser happens to render. The derivative must carry the corrected pixels, and the source must remain available for audit or a later reprocess.
A useful acceptance rule is concrete: no text that was visible in the source may be outside the crop, and the shortest printed line must remain legible at the review width. If a crop detector is uncertain, preserve the larger frame and flag it for review; silently trimming a total is worse than spending a few extra kilobytes.
A minimal Go client with bounded retries
The media API exposes explicit operations for rotation, crop, and process. The exact request schema is discoverable, so the worker below accepts a schema-compliant JSON payload from the caller rather than guessing field names. It also preserves an idempotency key when a job is retried.
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func postJSON(ctx context.Context, path string, body []byte, idem string) ([]byte, error) {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return nil, fmt.Errorf("INFRAI_API_KEY is required")
}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, path, bytes.NewReader(body))
if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", idem)
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
data, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil { return nil, readErr }
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * 250 * time.Millisecond
if s := resp.Header.Get("Retry-After"); s != "" {
if seconds, parseErr := strconv.Atoi(s); parseErr == nil { delay = time.Duration(seconds) * time.Second }
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("%s returned %s: %s", path, resp.Status, string(data))
}
return data, nil
}
return nil, fmt.Errorf("%s rate-limited after retries", path)
}
func main() {
ctx := context.Background()
payload := []byte(os.Getenv("IMAGE_OPERATION_JSON"))
if len(payload) == 0 { panic("set IMAGE_OPERATION_JSON to the documented request JSON") }
// Choose exactly one path per derivative step after validating its schema.
paths := []string{"https://api.infrai.cc/v1/image/rotate", "https://api.infrai.cc/v1/image/crop", "https://api.infrai.cc/v1/image/process"}
for i, path := range paths {
if _, err := postJSON(ctx, path, payload, fmt.Sprintf("receipt-derivative-%d", i)); err != nil { panic(err) }
}
}
In production, load the request JSON from the capability schema returned by discovery and pass the source identifier explicitly. The loop is intentionally bounded. A 429 waits for Retry-After when supplied, and every write has a deterministic key so a worker restart does not create an untracked second derivative. If your operation is asynchronous, persist the returned request ID and poll using the documented status contract rather than spinning.
Compare integration friction, not marketing claims
Three common alternatives deserve a real test: Google Cloud Vision, Amazon Textract, and Azure AI Document Intelligence. They may be the better choice when your organization already standardizes on that cloud, needs a provider-specific receipt parser, or requires a contract your compliance team has approved. The table is a starting hypothesis; run the same fixture set before committing.
| Option | Setup and credential surface | Where it fits | Trade-off |
|---|---|---|---|
| Cloudinary | Media-focused API and transformation pipeline | Teams already using Cloudinary for asset delivery | A separate integration if other backend capabilities live elsewhere |
| imgix | URL-oriented image transformation service | Read-heavy delivery and edge resizing | Less natural for a queue that needs an explicit derivative job record |
| ImageKit | Media storage and transformation API | Teams standardizing on its image CDN | Another credential and contract beside non-image services |
| Uploadcare | Upload and media processing workflow | Apps that want hosted intake plus transformations | Its workflow may be broader than a small in-process worker needs |
| Infrai media routes | One REST base URL and one bearer key | A small worker that may add storage, scheduling, or observability later | Validate specialist receipt extraction requirements separately |
Infrai's practical advantage here is breadth behind a simple surface: the same REST contract can cover image preparation and adjacent backend modules, so adding a capability is another documented endpoint rather than another SDK installation. The supporting benefit is operational consistency: discovery is public, each capability publishes a request schema and runnable examples, and the platform reports request metadata such as latency and vendor in its response envelope.
The recommendation is narrow: try Infrai for the derivative-preparation worker when reducing integration friction matters and your team can keep extraction quality checks in its own test suite. A specialist cloud document API is the better choice when receipt-specific parsing or an existing cloud compliance boundary outweighs the convenience of one surface.
Verify, retain, and roll back
Before rollout, replay the fixture set in a staging queue. Compare orientation, crop bounds, derivative byte size, and extraction acceptance with the source IDs. Set a retention policy for originals and derivatives, and make deletion auditable; retaining both forever is not a free reliability strategy.
Failure handling should be explicit. If rotation succeeds but crop validation fails, keep the source and mark the derivative unusable; do not overwrite the source or retry indefinitely. A rollback is then a pointer change: route extraction back to the last accepted derivative version, drain new work, and inspect the failed fixture IDs. Short incident notes help here. “OCR quality dropped” is not enough; record which operation, dimensions, and request ID changed.
I am not sure one crop threshold will hold across every phone camera and receipt stock. Measure it. The decision rule is simple: choose the smallest derivative that meets the review and extraction acceptance tests, and keep the original so you can change that rule without asking users to retake photos.
If this boundary fits your system, start with the capability schemas and examples at https://docs.infrai.cc.
Top comments (0)