DEV Community

김찬우
김찬우

Posted on Fully Autonomous

Design a business-lookup form that survives double clicks and stale responses

Disclosure: I operate APICK. This article was prepared with AI assistance and contains product promotion. The architecture below is a proposed integration pattern, not a benchmark, customer case study, or tested reference implementation.

A business-information lookup looks like a small feature: enter a registration number, click a button, and populate a form.

The interesting problems begin when someone clicks twice, edits the number while a request is running, or loses their connection after your server has sent the upstream request. A working happy path does not tell you which result belongs on the screen—or whether another request is appropriate.

Here is a way to design that boundary for a merchant-registration or supplier-onboarding form.

What APICK provides, and what your application owns

APICK's business-information API supports lookup by a business registration number. Its official integration guidance places the authentication key on the server, not in browser or mobile-app code.

Those are product capabilities and requirements. Request coalescing, frontend state management, caching, authorization, and merchant approval are application-level decisions in this article. Do not assume the provider implements the policies described below.

Keep the basic connection simple:

Browser form → Your authenticated backend → APICK
                       ↓
             Screen-specific response
Enter fullscreen mode Exit fullscreen mode

The browser should not receive your provider credential or an unrestricted proxy to the upstream API.

1. Define two contracts before writing the button

Your lookup contract answers: “What information did we retrieve for this business?”

Your onboarding contract answers: “Can this applicant proceed under our policy?”

Keep them separate. A completed lookup does not mean that contracts, product restrictions, payout requirements, or other onboarding checks have been satisfied.

For example, your application might use:

  • Lookup: idle, pending, found, no_match, unavailable.
  • Onboarding: draft, submitted, needs_review, approved.

These are illustrative internal states, not APICK response codes. Build an adapter that maps documented provider responses to your internal model without guessing what unfamiliar fields mean.

Also separate applicant-entered information from retrieved information. If a user edits a displayed company name, do not silently preserve a “verified” label on that edited value.

2. Coalesce clicks, but also reject stale responses

Disabling a button helps the interface, but it is not an idempotency guarantee. Another tab, a retry, or a separate client can still send a request.

For the current form, two different mechanisms are useful:

  1. In-flight coalescing: identical pending lookups share one request.
  2. Revision checking: a result updates the form only if the input has not changed since that lookup started.

The second catches a subtle bug: lookup A starts, the user changes the number to B, and A finishes after B is already on screen. Normalization should remove only permitted formatting; validate afterwards rather than stripping arbitrary invalid characters.

Illustrative pseudocode—not runnable SDK code:

onInputChanged(newValue):
    form.revision += 1
    form.number = normalize(newValue)
    form.result = empty

onLookupClicked():
    snapshot = copy(form.number, form.revision, session.generation)
    task = pending.get(snapshot.number)
    if task is absent:
        task = backend.lookup(snapshot.number)
        pending.set(snapshot.number, task)
    try:
        result = await task
        if snapshot.revision == form.revision
           and snapshot.generation == session.generation:
            form.result = result
    finally:
        remove pending[snapshot.number] only if it still refers to task
Enter fullscreen mode Exit fullscreen mode

Scope this temporary map to the current user and clear it when the session changes. Increment the session generation on logout or user switch. Apply both guards to error messages and loading-state cleanup. Keep map cleanup scoped to the originating session. An old request must not modify a new session’s interface.

Aborting a browser request does not prove that upstream processing stopped. Treat cancellation as a user-interface event unless you have an explicitly documented end-to-end cancellation contract.

3. Put enforcement on your backend

The server should authenticate the caller, authorize this specific lookup workflow, validate input, and enforce usage limits before contacting the provider. A logged-in account alone should not provide unlimited access.

Load the credential from server-side secret configuration. Exclude it from client bundles, example documents, tracing headers, and error messages. Prepare a rotation procedure rather than waiting for a leak to decide who owns it.

Authorize every caller before joining a shared backend request. Its sharing key should include normalized input, tenant/workflow, and any authorization boundaries or response-shaping options. A process-local map does not coordinate multiple application instances. Shared coordination needs bounded lifetimes and explicit failure handling.

Application deduplication also does not establish provider-level “exactly once” execution or billing. Do not send an invented idempotency header and assume it works. Verify supported upstream behavior before designing around it.

4. Make retries an explicit decision

Separate errors by what the user or operator can do next:

Internal category Suggested next action
Invalid input Correct the highlighted field.
No matching result Recheck the input; do not automatically accuse the applicant of fraud.
Authentication or configuration failure Stop retries and alert an operator without exposing credentials.
Temporary unavailability Offer a controlled retry when appropriate.
Timeout with uncertain outcome Explain that completion is unknown; apply a documented retry policy.

These categories are your product design, not a provider error-code reference. Use the official documentation to determine which responses justify each category.

Bound client waiting so an unresolved promise cannot pin the pending map forever. Expiry means an unknown outcome, not upstream cancellation or permission for immediate retry. Bound automatic retries too; repeated requests can amplify an outage. Never convert a transport failure into an empty business record and overwrite previously retrieved information.

5. Return less data, and record the right metadata

Have your backend return only the fields the current screen needs. A registration form and a restricted review console may require different views.

For operational logs, start with an internal correlation ID, outcome category, duration, and timestamp. Avoid default logging of request bodies, full provider responses, authentication headers, or raw customer records. Review error-reporting and tracing integrations too; application logs are not the only place data can escape.

If you retain lookup results, record when they were checked. Set access and retention rules for your purpose. A cached value should not be presented as a fresh lookup, and a temporary error should not become a cached negative result.

A useful first release

Start with one onboarding screen and a small review checklist:

  • Two clicks share a pending request in the current form.
  • Editing the input or switching users blocks old responses and UI cleanup.
  • Credential failures do not trigger uncontrolled retries.
  • Logs omit secrets and unnecessary personal information.
  • “Lookup completed” remains distinct from “Applicant approved.”

These are acceptance criteria to validate, not claims that we have run those tests here.

If your product needs Korean business-information lookup, review the APICK product page and linked development documentation. Start with the documented lookup capability, then build the application boundaries that make it understandable and controllable for your users.

Top comments (0)