Katkat

Propagate runtime ID across API requests

StripeSoftware Engineer · Technical Screen

Last updated

Implement a request interpolator that traverses nested request payloads and URLs to replace placeholder variables with runtime IDs while preserving native data types.

Propagate Runtime ID Across API Requests

When orchestrating end-to-end integration workflows, an API runner often executes a sequence of requests where downstream calls depend on IDs or values generated by earlier responses.

You are tasked with implementing a function interpolate_request that resolves template placeholders in an HTTP request template using values from a runtime context dictionary.

Requirements

  1. Placeholder Syntax:

    • Placeholders are formatted as {{path.to.key}}.
    • Keys are alphanumeric and may contain dots (.) to represent nested lookup in context (e.g., {{customer.default_source}} maps to context["customer"]["default_source"]).
  2. Type-Preserving Replacement:

    • If a string field matches exactly a single placeholder (e.g., "{{payment.amount}}"), it must be replaced with the actual runtime value and its original type (e.g., integer 5000, boolean true, or a nested object/list).
    • If a placeholder is embedded within surrounding text (e.g., "/v1/customers/{{customer.id}}/cards"), interpolate the string representation of the resolved value.
  3. Nested Data Traversal:

    • The request body can contain arbitrary JSON structures: dictionaries, lists, strings, numbers, booleans, and nulls.
    • You must recursively traverse and interpolate all placeholders within url, headers, and body.
  4. Missing Keys:

    • If any placeholder path does not exist in context, raise a KeyError.

Function Signature

def interpolate_request(request: dict, context: dict) -> dict:
    pass

Input and Output Format

  • request: A dictionary containing at least "url" (str), and optionally "headers" (dict[str, str]) and "body" (JSON-compatible data: dict, list, primitive, or None).
  • context: A nested dictionary containing runtime values produced by previous requests.
  • Returns a new dictionary with the exact same structure as request, where all placeholders have been replaced.

Example 1

request = {
    "url": "/v1/charges/{{charge.id}}/refund",
    "headers": {"Idempotency-Key": "refund-{{charge.id}}"},
    "body": {
        "amount": "{{charge.amount}}",
        "metadata": {
            "customer": "{{charge.customer.id}}",
            "full_refund": "{{charge.metadata.is_full}}"
        }
    }
}

context = {
    "charge": {
        "id": "ch_12345",
        "amount": 2500,
        "customer": {"id": "cus_9999"},
        "metadata": {"is_full": True}
    }
}

# Output:
{
    "url": "/v1/charges/ch_12345/refund",
    "headers": {"Idempotency-Key": "refund-ch_12345"},
    "body": {
        "amount": 2500,  # Preserved as int, not "2500"
        "metadata": {
            "customer": "cus_9999",
            "full_refund": True  # Preserved as bool
        }
    }
}

Constraints

  • request JSON depth <= 10.
  • Context keys are valid identifier strings matching [a-zA-Z0-9_]+.
  • String values may contain zero, one, or multiple placeholders.

Hints

Hint 1

Differentiate between strings that consist solely of a single placeholder `^\{\{([^}]+)\}\}$` and strings that have surrounding characters or multiple placeholders.

Hint 2

Write a helper function to resolve dot-separated paths (e.g. `"a.b.c"`) by recursively or iteratively traversing the context dictionary.

Hint 3

Recursively traverse the request structure: handle dictionaries, lists, and primitives (strings vs non-strings) as distinct cases.

Follow-up questions

  • How would you handle cyclic references in the context dictionary or circular dependencies across chained requests?
  • If placeholder replacement supported default values (e.g., `{{customer.name | 'Anonymous'}}`), how would you update your parser?
  • How would you optimize memory usage and performance if request bodies are large (e.g., megabytes of batch data)?