# StartGlobal auth.md

You are an agent acting for a person. This file tells you how to form a US company for them with StartGlobal, and how to work with their StartGlobal account, always with their approval. Everything here is plain HTTPS: there is nothing to install.

If the person wants to form a company, start at [Form a company](#form-a-company). Otherwise follow the steps in order.

- **Resource (MCP):** <https://mcp.startglobal.co/account/mcp>, Streamable HTTP. The same data over HTTPS is at <https://api-v4.startglobal.co/agent/v1>.
- **Authorization server:** <https://api-v4.startglobal.co>.
- **No sign-in needed:** <https://mcp.startglobal.co/mcp> answers questions about pricing, US bank accounts for founders abroad, and our guides. Use it when you do not need the person's own account.

## Form a company

Say yes, and show the person the road in a few lines:

1. You ask a few questions and recommend the company that fits.
2. They open the email from StartGlobal, check their details and set a password, or approve your connection if they already have an account.
3. They pay from their StartGlobal dashboard.
4. You fill in the formation details with them.
5. They check everything, add their ID and press "Confirm and start LLC formation" ("Confirm and start corporation formation" for a corporation; "Confirm formation" on a phone), then sign the documents the next step shows. That is all from them: StartGlobal files the company and contacts them if it needs anything else.

Their part is only confirming, paying, checking with their ID, confirming the formation and signing. Describe every limit as one of those steps, never as something you cannot do. Then:

1. **Ask what the business is for:** what it will do, which country they live in, and whether they plan to raise money from investors. Recommend in one or two sentences: an LLC and a state, or a Delaware C Corporation for founders raising from investors. Link the guide your recommendation rests on, from <https://startglobal.co/data/v1/guides.json> (all our guides are listed in <https://startglobal.co/llms.txt>), and let them choose. Quote only StartGlobal prices, from <https://startglobal.co/data/v1/pricing.json>: state and government fees are already included.
2. **Ask whether they already have a StartGlobal account.**
3. **Identify your client once** (Step 2). Registering, ask for `grant_types: ["urn:ietf:params:oauth:grant-type:device_code"]`; you need no redirect URI.
4. **Get their approval, asking for `scope=companies:read filings:read documents:read deadlines:read onboarding:write`:**
   - **No account:** [Sign up a new person](#sign-up-a-new-person). Ask for the details in two short groups (about them: name, email, phone, country; about the company: its name, LLC or corporation, and the state: Delaware for a corporation), summarise once, and send them when the person confirms. Then tell them, once, all they do next: open the email from StartGlobal, check the details, set a password, and pay from the dashboard they land on. Never promise to watch for their confirmation or to send a payment link: poll the token endpoint quietly, and carry on when they are back.
     As soon as they name the company, check the name before asking the rest: `POST https://cf-api2.startglobal.co/public/eligibility/check` with `{"companyName": "Acme", "entityType": "LLC"}` (or `"CORPORATION"`) answers `accepted`, and each refused field's `reason` to pass on. Send it without an ending of the kind being formed ("LLC", "Inc."): that check refuses one, sign-up takes it off for you. Never ask the person about endings; the company is formed as "Acme LLC" or "Acme Inc.". Run this check without announcing it: it checks the naming rules only, so never tell the person the name is available, or that you will check. StartGlobal checks that during formation.
   - **An account:** [Connect with a code](#connect-with-a-code). They choose the company when they approve. If the company they want to form is not on their account yet, they start it in the StartGlobal portal (https://app.startglobal.co) first.
5. **Read where it stands:** `GET https://api-v4.startglobal.co/agent/v1/onboarding`, and follow its `nextStep`.
   - **Not paid yet:** `paymentUrl` is set. They pay from their StartGlobal dashboard; give them `paymentUrl` only if they cannot find where to pay. When they say they have paid, read the onboarding again.
   - **Paid:** fill in the formation with them, a few questions at a time. See [Fill in onboarding](#fill-in-onboarding).
   - **Nothing missing:** give them `reviewUrl`, where they check every change, upload any ID still needed, press "Confirm and start LLC formation" ("Confirm and start corporation formation" for a corporation; "Confirm formation" on a phone) and sign the documents the next step shows. Once signed, that is all from them: StartGlobal contacts them if it needs anything else.
6. **Keep the connection:** a token carrying `onboarding:write` lives 15 minutes. Refresh it (Step 4) rather than asking the person again.

## What a connection allows

One connection covers one company, chosen by the person when they approve. These scopes read:

| Scope | What it reads |
|---|---|
| `companies:read` | The connected company: name, state, country, and the person's roles on it. |
| `filings:read` | Formation status: state filing, EIN, registered agent, filings in progress. |
| `documents:read` | Formation and EIN documents and the latest operating agreement, with short-lived download links. |
| `deadlines:read` | Compliance standing, services and their renewal dates, upcoming dues. |

One scope writes: `onboarding:write` lets you fill in a new company's formation details for the person. See [Fill in onboarding](#fill-in-onboarding). The MCP server's sign-in asks for it along with the reads; over plain OAuth, ask for it by name.

Nothing you can hold files, signs or pays anything: the person does those steps themselves at https://app.startglobal.co. Do not ask for other scopes; the request is refused with `invalid_scope`.

Keep one connection per company. Connecting again to the same company, by any method, replaces your earlier connection: its access token is refused with `invalid_token` and its refresh token with `invalid_grant`. Use the tokens from the newest connection and discard the rest.

## Step 1: Discover

Call <https://mcp.startglobal.co/account/mcp> without a token. The `401` carries the metadata URL:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.startglobal.co/.well-known/oauth-protected-resource/account/mcp", scope="companies:read filings:read documents:read deadlines:read onboarding:write"
```

1. Fetch that Protected Resource Metadata (RFC 9728). Its `resource` is the audience to ask for; its `authorization_servers` names <https://api-v4.startglobal.co>. The HTTPS API's is <https://api-v4.startglobal.co/.well-known/oauth-protected-resource/agent/v1>.
2. Fetch <https://api-v4.startglobal.co/.well-known/oauth-authorization-server> (RFC 8414) for the endpoints below.

## Step 2: Pick a method

Every connection is approved by the person. There is no agent-only registration: this service does not offer auth.md's `anonymous`, `service_auth` or `identity_assertion` methods.

- **The person wants to form a company:** [Form a company](#form-a-company) takes you through the methods below.
- **The person has an account:** Steps 3 to 5.
- **The person has an account, and you cannot open a browser or receive a redirect** (a CLI agent on a server, over SSH, in a container): see [Connect with a code](#connect-with-a-code).
- **The person has no account:** start one for them and let them confirm it by email. See [Sign up a new person](#sign-up-a-new-person). They can also sign up themselves at https://app.startglobal.co/signup.

Identify your client in one of two ways:

1. **Client ID Metadata Document (preferred).** Use the https URL of your metadata document as `client_id`. Its `client_id` must equal that URL and it must list your `redirect_uris`; `client_name` and `logo_uri` are shown to the person. We fetch it over https with no redirects, within 5 seconds, up to 64 KB, and cache it for 24 hours.
2. **Dynamic Client Registration (RFC 7591).** POST <https://api-v4.startglobal.co/agent/oauth/register> with JSON. Public clients only: `token_endpoint_auth_method` is `none`. Redirect URIs are https, or http on a loopback address. A client that only connects with a code registers `grant_types: ["urn:ietf:params:oauth:grant-type:device_code"]` and needs no `redirect_uris`. The response is `201` with your `client_id`.

Clients are not pre-approved. The consent screen marks a client we do not recognise as unverified.

## Step 3: Ask the person

Send the person's browser to the authorization endpoint (OAuth 2.1, PKCE with S256 required):

```http
GET https://api-v4.startglobal.co/agent/oauth/authorize
  ?response_type=code
  &client_id=<your client_id>
  &redirect_uri=<a registered redirect URI>
  &code_challenge=<BASE64URL(SHA256(code_verifier))>
  &code_challenge_method=S256
  &state=<random>
  &scope=companies:read filings:read documents:read deadlines:read
  &resource=https://mcp.startglobal.co/account/mcp
```

The person signs in to the StartGlobal portal, picks one company, sees your name and the scopes, and approves or denies. They have 30 minutes, long enough to create an account from the sign-in screen first. You get `redirect_uri?code=...&state=...&iss=https://api-v4.startglobal.co`: check `state` and that `iss` equals <https://api-v4.startglobal.co>. A denial returns `error=access_denied`.

Omitting `scope` asks for the four read scopes. Omitting `resource` gets a token for the MCP server. Errors found before your client and redirect URI are validated come back as JSON on the authorization server, never as a redirect.

## Step 4: Exchange the code

The code is single-use and lives 60 seconds.

```http
POST https://api-v4.startglobal.co/agent/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=<code>&redirect_uri=<same URI>&client_id=<your client_id>&code_verifier=<verifier>&resource=https://mcp.startglobal.co/account/mcp
```

The response has an `access_token` (a JWT, `typ at+jwt`, one hour, or 15 minutes when it carries `onboarding:write`), a `refresh_token` (30 days), `scope` and `expires_in`.

To refresh: `grant_type=refresh_token&refresh_token=<token>&client_id=<your client_id>`. Every refresh returns a new refresh token; keep only the newest. Presenting a refresh token that was already used revokes the whole connection.

## Step 5: Use the access token

Send it as `Authorization: Bearer <access_token>`, in the header only.

- **MCP:** connect to <https://mcp.startglobal.co/account/mcp>. You see the public tools plus `get_my_company`, `get_filing_status`, `list_documents`, `get_document_link` and `get_deadlines`, each only when your token carries its scope.
- **HTTPS:** GET <https://api-v4.startglobal.co/agent/v1/company>, `/filings`, `/documents`, `/documents/{id}/download-link`, `/deadlines`. A token for the MCP server is accepted here too.

Download links work for five minutes. Give them to the person to open; do not store or share them.

Every call is checked against the connection, so a disconnect takes effect on your next call, not when the token expires.

## Fill in onboarding

After a person pays for a new company, StartGlobal needs their formation details: company info, the responsible party, the members (an LLC) or directors, officers, incorporator and shares (a corporation), the beneficial owners and their ID documents. With `onboarding:write` you can fill these in for them.

- **Ask for it:** connecting to the MCP server asks for it already. Over plain OAuth, add `onboarding:write` to `scope` in Step 3 or in the device request. The consent screen warns the person that you will be able to change their details. Only the person who signed up for the company can grant it.
- **When it works:** a new company whose formation is paid and whose details are still being collected or checked. Before payment every write is refused with `409 not_editable`, and the onboarding's `paymentUrl` is where the person pays. Once the person has submitted, or for a company that is already formed, every write is refused with `409 not_editable` and a reason you can pass on.
- **Ask the person for every detail.** Never guess, invent or complete a name, address, date or ID number. Take their name as they write it, in English letters: it need not match their passport letter for letter, and there is no middle name field. The ID they upload is the proof. An SSN or ITIN is write-only: every read returns it masked.
- **The person is told.** Whoever approves you gets an email naming you, what you can do and where to disconnect you.
- **Nothing is filed by you.** Each save applies at once, and nothing reaches the state until the person opens the review link, sees every change you made beside what it was before, confirms each ID document you uploaded is theirs, confirms the formation and signs its documents themselves.

Over MCP, the tools are `get_onboarding`, one `save_*` tool per step and `upload_identity_document`. Call `get_onboarding` first.

Over HTTPS:

- `GET https://api-v4.startglobal.co/agent/v1/onboarding`: the steps for this company, what is missing, what is saved, your changes so far, `reviewUrl`, `nextStep` (what to do now) and, while the formation is unpaid, `paymentUrl`. `missingFields` names each field still empty by its path (`company.website`, `members[0].address.postalCode`), so ask the person for exactly those; `readyForReview` is true once nothing is missing.
- `PUT https://api-v4.startglobal.co/agent/v1/onboarding/<step>` with JSON, one call per step. Each step's schema is at <https://mcp.startglobal.co/onboarding-schema.json>. A single-record step keeps any field you leave out; a list step (members, directors and officers, beneficial owners) replaces the whole list, so send every entry with its `id`. Every answer is the onboarding as it then stands.
- **Industry** is one of StartGlobal's industries, the list the person sees in the portal: the onboarding lists every name in `data.company.industries`. Pick the closest with the person and send that name; any other value is refused with the list.
- **Company names** follow StartGlobal's name rules, checked when you save: at most 100 characters, and the alternate name differs from the first. Both are required before the person can submit.
- **Website** is required before the person can submit, written like `www.example.com` or `https://example.com`.
- **Business description** is 10 to 150 characters. It goes on a US government filing, so English letters, numbers and common punctuation only.
- **Every address needs a postal code.** If the address has none, send `00000`.
- **A refused save names every problem** in `error_description`, field by field (`website: ...`). Fix them all with the person and send the step again.
- `POST https://api-v4.startglobal.co/agent/v1/onboarding/documents`, `multipart/form-data` with `purpose` (`responsible-party-id` or `beneficial-owner-id`) and `file` (PDF, JPEG or PNG, at most 10 MB). Put a beneficial owner's `documentId` in that owner's `identityDocumentId`. The person's ID and every beneficial owner's must be on file before they can submit. If you cannot send a file (a chat connector cannot), ask the person to upload it on `reviewUrl`, which lists each ID still needed.

When `readyForReview` is true, give the person `reviewUrl`.

## Connect with a code

For a person who has an account, when you have no browser to send them to (RFC 8628). You show them a short code; they type it on the StartGlobal portal, sign in if needed, pick one company and approve or deny, exactly as in Step 3. You poll for the answer.

### Start

```http
POST https://api-v4.startglobal.co/agent/oauth/device_authorization
Content-Type: application/x-www-form-urlencoded

client_id=<your client_id>&scope=companies:read filings:read&resource=https://mcp.startglobal.co/account/mcp
```

```json
{
  "device_code": "…",
  "user_code": "BCDF-GHJK",
  "verification_uri": "…/agent/device",
  "verification_uri_complete": "…/agent/device?code=BCDF-GHJK",
  "expires_in": 600,
  "interval": 5
}
```

`scope` and `resource` work as in Step 3. Show the person `user_code` and `verification_uri`, or `verification_uri_complete` as a link or QR code. The portal still asks them to check the code against yours before they continue. Keep `device_code` to yourself. The code lives 10 minutes; after that, start again.

### Poll

Every `interval` seconds, no faster:

```http
POST https://api-v4.startglobal.co/agent/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=<device_code>&client_id=<your client_id>
```

| Answer | Meaning |
|---|---|
| `authorization_pending` (400) | Not answered yet. Poll again after `interval`. |
| `slow_down` (400) | You polled inside the interval. Wait longer. |
| `access_denied` (400) | The person denied. Stop. |
| `expired_token` (400) | The code expired or was already used. Start again. |
| `invalid_grant` (400) | The device code was issued to another client. |
| `200` with tokens | Approved. Same response as Step 4; use them as in Step 5. |

## Sign up a new person

For someone with no StartGlobal account. You send their details; we email them a link; they check the details, choose their own password and accept our terms. Nothing is created until they do. Meanwhile you poll the token endpoint, and once they confirm you receive tokens for their new company, as if they had approved Step 3. You never see or set their password.

You need a `client_id` first (Step 2).

### Start

```http
POST https://api-v4.startglobal.co/agent/oauth/signup
Content-Type: application/json

{
  "client_id": "<your client_id>",
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace",
  "phone": "+14155550100",
  "residence_country": "IN",
  "entity_type": "LLC",
  "formation_state": "Wyoming",
  "company_name": "Analytical Engines",
  "scope": "companies:read filings:read documents:read deadlines:read",
  "resource": "https://mcp.startglobal.co/account/mcp"
}
```

Only `client_id` and `email` are required. Send whatever else the person already told you; they can correct every field before confirming. `residence_country` is a country name or an ISO 3166 alpha-2 code. `entity_type` is `LLC` or `CORPORATION`. `formation_state` is a US state name. `company_name` is the name as the person said it: an ending of the kind being formed ("Acme LLC") is taken off, since StartGlobal adds its own. `scope` and `resource` work as in Step 3.

```json
{ "signup_id": "…", "device_code": "…", "expires_in": 3600, "interval": 5 }
```

Tell the person to check their email. The link, and your wait, end after `expires_in` seconds.

We check the details against the same rules as our own sign-up form before any email goes out. A refusal is `invalid_request` with the reason in `error_description`, written so you can pass it on: a country we cannot serve, a company name we cannot form, an email address we cannot accept.

If the address already has an account, the answer is exactly the same: we email that person to sign in instead, and your poll ends in `expired_token`. Then connect them with Steps 3 to 5.

### Poll

Every `interval` seconds, no faster:

```http
POST https://api-v4.startglobal.co/agent/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=<device_code>&client_id=<your client_id>
```

| Answer | Meaning |
|---|---|
| `authorization_pending` (400) | Not confirmed yet. Poll again after `interval`. |
| `slow_down` (400) | You polled inside the interval. Wait longer. |
| `access_denied` (400) | The person said the sign-up was not them. Stop. |
| `expired_token` (400) | Past `expires_in`, or the address already had an account. Stop. |
| `200` with tokens | Confirmed. Same response as Step 4; use them as in Step 5. |
| `invalid_grant` (400) | Tokens were already issued for this device code, or it is unknown. Tokens are issued once. |

The new company starts unpaid. Confirming lands the person on their StartGlobal dashboard, where they pay themselves, never through you, so there is no payment link to send. Read the onboarding (`GET https://api-v4.startglobal.co/agent/v1/onboarding`, with `onboarding:write`): once it is paid, fill it in with them. If they come back without having paid, give them its `paymentUrl`.

## Errors

| Code | Where | What to do |
|---|---|---|
| `invalid_token` (401) | resource | Refresh once. If refreshing fails, go back to Step 3. |
| `insufficient_scope` (403) | resource | The person did not grant that scope. Ask them to connect again with it. |
| `access_disabled` (403) | anywhere | Agent access is switched off for your client or for everyone. Stop; do not retry. |
| `not_found` (404) | resource | Nothing this connection can see has that id. |
| `temporarily_unavailable` (503) | anywhere | Our outage. Retry later; do not start the flow again. |
| `invalid_grant` | token | Code or refresh token expired, used, or revoked. Go back to Step 3. |
| `invalid_client`, `invalid_redirect_uri` | authorize, token | Fix your client_id or redirect URI (Step 2). |
| `invalid_scope`, `invalid_target` | authorize, token | Ask only for the scopes and resources listed here. |
| `unsupported_grant_type` | token | Use `authorization_code`, `refresh_token` or the device code grant. |
| `invalid_request` | signup | A field is malformed, or the person cannot sign up with these details. Read `error_description`. |
| `invalid_request` | device_authorization | `client_id` is missing. |
| `invalid_request` (400) | onboarding | A detail is missing or malformed. `error_description` says what, in a sentence you can pass on; when the platform refused it, `code` carries its own error code too (`POSTAL_CODE_NOT_SPECIFIED`, `OWNERS_PERCENTAGE_IS_NOT_COMPLETE`). Ask the person and send the step again. |
| `not_editable` (409) | onboarding | This company's details can no longer be changed by you. Pass `error_description` on to the person. |
| `file_too_large` (413), `unsupported_file_type` (415) | onboarding documents | Send a PDF, JPEG or PNG of at most 10 MB. |
| `slow_down` (429) | authorize, token, register, signup, device_authorization | Back off before retrying. |
| `slow_down` (429) | onboarding | This connection passed 60 saves or 20 ID uploads in an hour, refused ones included. Stop and wait; the person can also finish in the portal. |

Every error above is JSON. A response that is not `application/json`, such as a 403 whose body is `error code: 1010`, came from the network in front of these servers, not from them: check `Content-Type` before parsing, and report the status and body instead of retrying in a loop.

## Revocation

- **You:** POST <https://api-v4.startglobal.co/agent/oauth/revoke> with `token=<access or refresh token>&client_id=<your client_id>` (RFC 7009). It ends the connection and always answers `200`.
- **The person:** disconnects you in the StartGlobal portal. You find out from `invalid_token` on your next call, or `invalid_grant` on your next refresh.
