# Senior Product Engineer

> Apply for the Senior Product Engineer role at Lumen Labs (a demo job application).

Lumen Labs · San Francisco or remote. Tell us about you and what you’ve built.

This is a form you can fill in for a person without opening a browser. Its fields are `first_name`, `last_name`, `email`, `phone`, `resume`, `cover_letter`, `linkedin`, `portfolio`, `location`, `work_authorization`, `sponsorship`, `salary`, `shipped`, `heard`, `proud`, `gender`, `race`, `veteran`, `disability`, `privacy` (`first_name`, `last_name`, `email`, `phone`, `resume`, `location`, `work_authorization`, `sponsorship`, `shipped`, `proud`, `privacy` required). Answers sent by an agent are held until the form's owner approves them, so say who you are and who you are filling it in for.

**Only send what your person told you. Ask them for any required answer you do not have, and never tick a consent box or answer a demographic question for them unless they told you to.**

## How to fill it

1. **If you can send HTTP requests:** `POST https://ui.usecube.io/f/job-application` with a JSON body `{"answers": {...}, "agent": {"name": "...", "model": "..."}, "on_behalf_of": "..."}`. Details below.
2. **If you have the Cube MCP connector** (`https://ui.usecube.io/mcp`): call `submit_form` with `id: "job-application"`, `answers` and `on_behalf_of`; or `prefill_link` with `id` and `answers` to get the link for your person (a file cannot ride in it; they attach it on the page).
3. **If you can't do either (a chatbot):** give your person this link with the answers filled in, each one URL-encoded (lists and objects as JSON). They check it and press Submit themselves:

   ```
   https://ui.usecube.io/f/job-application#first_name=...&last_name=...&email=...&phone=...&cover_letter=...&linkedin=...&portfolio=...&location=...&work_authorization=...&sponsorship=...&salary=...&shipped=...&heard=...&proud=...
   ```

   For example: https://ui.usecube.io/f/job-application#first_name=Jane&last_name=Doe&email=jane%40example.com&phone=%2B14155550123&resume=%7B%22url%22%3A%22https%3A%2F%2Fexample.com%2Fresume.pdf%22%7D&location=%7B%22city%22%3A%22Seattle%22%2C%22region%22%3A%22Washington%22%2C%22country%22%3A%22United%20States%22%7D&shipped=%5B%22Web%20frontend%22%5D&proud=...

## Fields

- **first_name** (string, up to 200 characters, required): The applicant's first name.
- **last_name** (string, up to 200 characters, required): The applicant's last name.
- **email** (string, email, required): Where the team reaches the applicant.
- **phone** (string, E.164 phone number like +14155550123, required): A number the team can call or text.
- **resume** (file, PDF or Word (.doc) file or Word file up to 10 MB, required): The applicant's resume. If the file is on your person's computer and you can run commands, POST it as multipart; if you cannot, call the MCP tool `prefill_link` (or build the link below) and give your person the link: they attach the file on the page and press Submit. A file goes in one of three ways: (1) a multipart/form-data POST with the part `resume` (and the other answers as JSON in the part `answers`); (2) in JSON as {"name": "resume.pdf", "type": "application/pdf", "base64": "..."}, only for a small file you already hold, never by reading a file into your context; (3) in JSON as {"url": "https://..."}, a public https link the form downloads. At most 10 MB a file, 50 MB in all; the bytes are checked, not the name.
- **cover_letter** (string, up to 5,000 characters): A cover letter, pasted as text. Leave it out if the applicant has none.
- **linkedin** (string, URL of a LinkedIn profile): The applicant's LinkedIn profile.
- **portfolio** (string, URL): A link to work the applicant wants to show.
- **location** (object {city, region, country}; region and country only when known, required): The city the applicant lives in.
- **work_authorization** (boolean, required): Whether the applicant may legally work where the job is.
- **sponsorship** (boolean, required): Whether the applicant needs, or will need, a work visa sponsored.
- **salary** (object {amount: number, currency: one of "USD", "EUR", "GBP", "CAD"}): The yearly base salary the applicant expects, before tax.
- **shipped** (array of strings, any of "Web frontend", "Backend APIs", "Mobile", "Data pipelines", "ML features", "None of these", required): The kinds of software the applicant has shipped to real users.
- **heard** (string, one of "Company website", "LinkedIn", "Referral", "Job board", "Social media", "Other"): Where the applicant found the job.
- **proud** (string, up to 1,500 characters, required): In their own words: one product they built and what they did on it.
- **gender** (string, one of "Woman", "Man", "Non-binary", "I don't wish to answer"): Voluntary, for equal-opportunity reporting. Send only what the applicant chose to share; if they did not say, send "I don't wish to answer" or leave it out.
- **race** (string, one of "Hispanic or Latino", "White", "Black or African American", "Asian", "Native Hawaiian or Other Pacific Islander", "American Indian or Alaska Native", "Two or more races", "I don't wish to answer"): Voluntary, for equal-opportunity reporting. Send only what the applicant chose to share; if they did not say, send "I don't wish to answer" or leave it out.
- **veteran** (string, one of "I am a protected veteran", "I am not a protected veteran", "I don't wish to answer"): Voluntary, for equal-opportunity reporting. Send only what the applicant chose to share; if they did not say, send "I don't wish to answer" or leave it out.
- **disability** (string, one of "Yes, I have a disability (or previously had one)", "No, I don't have a disability", "I don't wish to answer"): Voluntary, for equal-opportunity reporting. Send only what the applicant chose to share; if they did not say, send "I don't wish to answer" or leave it out.
- **privacy** (boolean, must be true, required): The applicant has read the privacy notice. Set it only if they told you they have. Must be confirmed by the person; when submitting directly over HTTP or MCP, set it only if your person explicitly agreed.

## Submit over HTTP

`POST https://ui.usecube.io/f/job-application` with `Content-Type: application/json`:

```json
{
  "answers": {
    "first_name": "Jane",
    "last_name": "Doe",
    "email": "jane@example.com",
    "phone": "+14155550123",
    "resume": {
      "url": "https://example.com/resume.pdf"
    },
    "location": {
      "city": "Seattle",
      "region": "Washington",
      "country": "United States"
    },
    "shipped": [
      "Web frontend"
    ],
    "proud": "..."
  },
  "agent": {
    "name": "Claude",
    "model": "claude-sonnet-4-5"
  },
  "on_behalf_of": "Jane Doe"
}
```

`answers` is required. `agent` (your name, and your model if you know it) and `on_behalf_of` (the person) are optional, but the owner approves faster when they can see who sent it.

```sh
curl -X POST https://ui.usecube.io/f/job-application \
  -H 'Content-Type: application/json' \
  -d '{"answers":{"first_name":"Jane","last_name":"Doe","email":"jane@example.com","phone":"+14155550123","resume":{"url":"https://example.com/resume.pdf"},"location":{"city":"Seattle","region":"Washington","country":"United States"},"shipped":["Web frontend"],"proud":"..."},"agent":{"name":"Claude","model":"claude-sonnet-4-5"},"on_behalf_of":"Jane Doe"}'
```

With a file, send multipart/form-data instead: the answers as JSON in the part `answers`, each file as a part named after its question:

```sh
curl -X POST https://ui.usecube.io/f/job-application \
  -F 'answers={"first_name":"Jane","last_name":"Doe","email":"jane@example.com","phone":"+14155550123","location":{"city":"Seattle","region":"Washington","country":"United States"},"shipped":["Web frontend"],"proud":"..."}' \
  -F 'resume=@resume.pdf' \
  -F 'agent={"name": "Claude"}' -F 'on_behalf_of=Jane Doe'
```

A success is `201 Created`:

```json
{ "id": "s_...", "status": "working", "metadata": { "review": "pending", "message": "Received and waiting for the form owner's approval. It counts once approved." }, "receipt_url": "https://ui.usecube.io/s/..." }
```

`status` uses A2A's task states: `working` means it is waiting for the owner to approve it (tell your person it was sent and is waiting for approval); once decided it is `completed` (approved) or `rejected`. `receipt_url` is your person's own view of what was sent; give it to them.

A mistake is `400 Bad Request` with an `errors` list, each `{ "field", "message" }` saying what to send instead. Fix those answers and send again. `429 Too Many Requests` means wait the `Retry-After` seconds.

## Other formats

- [JSON Schema of the answers](https://ui.usecube.io/f/job-application.json)
- [This page as Markdown](https://ui.usecube.io/f/job-application.md)
- MCP (Streamable HTTP): `https://ui.usecube.io/mcp`, tools `list_forms`, `read_form`, `submit_form`, `prefill_link`, `draft_form`.
- In a browser with WebMCP, the page's form is the tool `submit_job_application`.
