Skip to content

StudioImport an API

Import an API

Turn an OpenAPI 3, Swagger 2 or Postman specification into typed API code, and keep it up to date as the specification changes.

27 min readboth modes

Importing

Import API on the APIs page turns an OpenAPI 3, Swagger 2 or Postman specification — from a URL, a file, or pasted text — into typed API code: pick the endpoints, choose the options, preview the files, then write them. Studio generates the files itself and writes them to the output directory, in a folder named after the source in kebab case (src/api/pets/ for pets, src/api/pet-store/ for petStore), where source.ts exports the source, and records what it wrote in .courier/imports-state.json — see Which files Studio changes.

Studio lists only the sources courier.config.ts declares — it does not look for API files in the project. So after the write the wizard shows the change that declares the import there — the import of the generated source, its sources entry and its imports entry — and Add to courier.config.ts makes it: Studio adds those lines and nothing else, written like the rest of the file, loads the config, and the API appears on the APIs page, in the Explorer and in the Playground. A config saved after the change was shown is never overwritten — the wizard shows the change against it as it is now — and one that does not load once changed is put back as it was. A config that only passes another module's default export on — export { default } from "./src/courier/config", as a server whose TypeScript compiles src/ alone has it — is changed where its object is written, in that module, so the server registers the import too. When the config is written in a way Studio cannot add to safely, such as sources built by a function call, or the browser is not on the machine Studio runs on, the wizard shows the lines to add by hand and says why. Either way the config ends up like this:

ts
// courier.config.ts
import { defineConfig } from "@noverax/courier/config"
import { api } from "./src/api"
import { pets } from "./src/api/pets/source"

export default defineConfig({
  sources: { api, pets },
  imports: {
    pets: {
      importType: "url",
      type: "openapi",
      from: "https://specs.example.com/pets.json",
      outputDir: "src/api",
      sourceName: "pets",
    },
  },
})

A config that keeps its list of APIs in a module of the project — import { sources } from "./lib/sources", by a relative path or a tsconfig path alias, the module exporting the object as export const sources = { … }, as a default export, or under another name it imports by — is changed in both files as one change. The generated source is imported and listed in that module, the imports entry goes in the config, the wizard shows the lines each file gains, and the button reads Add to sources.ts and courier.config.ts. Studio writes both files or neither: a save to either after the change was shown changes neither, and when the config does not load once changed, both are put back as they were. A module that builds the list by a function call, or that is a package's rather than the project's, is left to you with the lines to add, each under the file it goes in:

ts
// lib/sources.ts
import { api } from "../src/api"
import { pets } from "../src/api/pets/source"

export const sources = { api, pets }
ts
// courier.config.ts
import { defineConfig } from "@noverax/courier/config"
import { sources } from "./lib/sources"

export default defineConfig({
  sources,
  imports: {
    pets: {
      importType: "url",
      type: "openapi",
      from: "https://specs.example.com/pets.json",
      outputDir: "src/api",
      sourceName: "pets",
    },
  },
})
  • sources is what Studio lists APIs from, and what your app calls. An API written by hand is declared there the same way. Studio matches an import to the source of its name, so an import needs a name no other source takes: while sources declares another API under it, Studio writes nothing and says so. The APIs page lists a source as imported only when the config loads the source.ts Studio generated for it.
  • imports is what Studio checks and manages an import from: the update check fetches from again, and when the specification changed, Review update — Manage given a newer specification — updates the import to it: every endpoint you keep is generated from it afresh, so its types and schemas follow the specification, while an endpoint you edited keeps your code; you tick the new endpoints to add and untick the ones to drop; the endpoints the specification no longer has go, and Studio keeps a copy of every file it changes. The endpoints you add are generated with the options written there, and the ones you left out when you imported stay out. With the same specification and nothing picked, Manage offers Regenerate files: every file written again as the running version of Studio writes it, your edits to endpoints kept — the way to bring an import written by an older Studio up to date. The wizard's entry carries the source name and every option you changed from its default, but not the endpoints you left out in the Select step — the entry has no way to keep that selection — so Manage lists those as endpoints you can add, and the wizard says how many there are. A file or pasted import has no from: Manage asks for its specification each time, and the update check marks it not checked, rather than up to date. Studio keeps each import's last check while it runs, and every page shows it, one opened or reloaded later too; a check that fails says why on the import's card. The update check sends auth with the request to from's own origin, never to a host it redirects to, and refuses a specification whose hash is not the one pinHash names — the hash Studio records for the import as specHash in .courier/imports-state.json. An update, Manage and a new write of a declared import generate from what its transform returns for the parsed specification, keeping only the endpoints include names, by tag, by path as the specification writes it, or by operationId, and then leaving out those exclude names.
  • Importing the same name again keeps the endpoints you edited by hand, while the specification still describes them, and the ones you added, in the files that declare them. Every other generated file — the source of an API grouped by tag or path, its index.ts, the schemas — is written as generated; when it holds edits of yours, Studio asks before replacing it. A group left out this time has its files removed, as long as Studio wrote them and nobody changed them; the message after the write says how many files it removed, and their copies are kept.
  • A part the config declares already is left out, and once it declares both, the wizard closes after the write and says only what it wrote.
  • A tag that names a parent, as OpenAPI 3.2 allows, is imported as a group inside its parent tag's group: its own folder, listed under groups in the parent's file, its prefix after the parent's, so client.billing.invoices.list() reaches the path the specification gives. A tag only other tags sit under becomes a group of its own, and a parent the specification does not declare, or one that would put a tag under itself, is left out. Manage adds a new inner group to its parent's file and takes a removed one out of it; a group whose endpoints all go stays while groups sit inside it, and goes once they do.
  • Every operation whose verb the builder has is imported: GET, HEAD, OPTIONS, POST, PUT, PATCH, DELETE, and QUERY — OpenAPI 3.2's query operation, or a QUERY a specification lists under additionalOperations. An operation that reads is generated as a read, one addressed by a closure included. One the builder has no verb for — TRACE, or another verb under additionalOperations — is listed among the operations left out, with the reason, rather than dropped.
  • Each endpoint keeps what its operation says it does — the summary, or the first paragraph of the description — as .meta({ description: "…" }), whether or not it becomes a doc comment too: a tool the project's agents policy offers without .agent() describes itself to the model by it, and the OpenAPI written from the definition keeps it.
  • An operation is called with its path and query parameters and its header parameters, beside its body: a header parameter is a variable under its own name, validated with the rest and sent as a request header by headerVariablesMiddleware. Accept, Content-Type and Authorization are not taken as parameters, as OpenAPI says — the client sets them — and neither is a header named like a path or query parameter of the same operation, nor one beside a multipart form, which is the whole of what such a call is made with.
  • A schema with readOnly or writeOnly fields, itself or through a schema it refers to, is two types: the answer, User, without the fields only the client sends and with the ones the server sends as required as declared, and the input a request sends, UserInput, the other way round — named UserInput2 when the specification has a schema called UserInput. A schema that is only ever answered has no input type.
  • What a schema allows beyond its type — a string's minLength, maxLength and pattern; a number's minimum, maximum, exclusive bounds and whole multipleOf, and that an integer is whole; a list's minItems and maxItems — is checked on what a request sends, and never on what an answer carries: a validator that refuses what the API really sends is worse than none. A schema both sent and answered with such a constraint, itself or through a schema it refers to, is then two schemas in the same way: Product reads the answer and ProductInput checks the request. An operation without a body has its parameters validated when one of them carries such a constraint, and only typed otherwise. Left unchecked: uniqueItems, a multipleOf that is not a whole number, which floating point cannot check exactly, and a pattern JavaScript cannot compile.
  • Endpoints are grouped by the specification's tags, by path, or not at all, with Single file. By path, an endpoint goes in the group its resource names: the first segment of its path after the ones every path shares above every operation — Stripe's /v1, an API served under /api — that names neither a version, such as v2, v1beta1 or 2010-04-01, nor a parameter. So Stripe's endpoints go in customers, charges and the rest of its 78 resources, while an API whose every path starts with /settings, one of them /settings itself, keeps them all in settings.
  • An endpoint in no group sits in the API itself: an operation with no tag, a path no segment groups — the API's root /, or /v1 alone — a request at a Postman collection's root, and with Single file every endpoint. source.ts holds it beside the groups, and the client calls it on itself: client.health(), beside client.pets.list(). The wizard and Manage list these endpoints at the top level; adding the first of them to an import gives its source.ts an endpoints object beside groups, and removing the last takes the object away again with what only it needed. A specification that tags none of its operations — Stripe's, say — is grouped by path unless your settings choose a single file: by tag, none of its endpoints would be in a group, which the wizard says when tag is chosen anyway.
  • A schema only one group's endpoints use is declared in that group's folder. One that several groups use, or the API's own endpoints, is declared once, in the import's own schemas.ts, and each group imports it from there: declared in every group that used it, Stripe's schemas — nearly all of which refer to each other — came to 59 MB over its 78 resources; declared once, they are 4.7 MB, 1.5 MB of it the import's own schemas.ts, which Biome checks only with files.maxSize raised past its default of 1 MiB. Where each schema goes is decided over the whole specification, whatever you select, so endpoints added later find their schemas where the first write put them; a schema leaves the import's schemas.ts only once nothing left in the import uses it.
  • An import writes its folder under outputDir: source.ts, the API as the specification describes it — its own endpoints and its groups; schemas.ts, the schemas its own endpoints and several groups use; index.ts, the direct caller with the schemas and types, re-exported by name; proxy.ts, the proxy caller alone; and a folder for each group, with its endpoints and the schemas only it uses. Import the proxy caller in browser code from proxy.ts: it names the source only as a type, so the browser bundle holds no part of the source — not its upstream address, its middleware nor a schema — which importing it from index.ts would bring along with the direct caller and the schemas. For an API with streams, proxy.ts also exports a boundary, <name>Boundary, naming every endpoint and how it reaches the wire, which the proxy caller is given: a stream opens a connection of its own, so the caller has to know which endpoints are streams. The boundary refuses nothing on the server — give the source one of its own with defineApi({ boundary }) for that — and Manage writes proxy.ts again whenever it adds or removes endpoints, so the boundary names the endpoints the import has. Types are imported from index.ts with import type, which a bundle drops.
  • The generated code passes Biome's recommended rules, stricter ones and its import organizer as it is written, before any formatter runs: an import's index.ts re-exports each schema and type by name rather than with export *, a pattern is a regular expression literal, a file that imports only types imports them with import type, and imports, re-exports and the names in each come in the order the organizer keeps them. It compiles with noUnusedLocals, verbatimModuleSyntax and exactOptionalPropertyTypes on.
  • The generated files are application code. A project that emits declarations compiles them, since every type their exports carry is one the packages export, but not one under isolatedDeclarations, which asks for a written type on every export: the type of a group or a source is the whole chain of its endpoints.

Which files Studio changes

Studio changes an import's files — when it writes the import, updates it, adds or removes endpoints, or removes the import — only inside the import's folder, and decides for each file by who it belongs to. It records every file it writes, with a hash of its content, in .courier/imports-state.json, and starts each with two lines that say so:

ts
// @courier-generated {"import":"pets","sha256":"3f2a…"}
// Courier Studio wrote this file. It keeps your edits to endpoints when it updates the file.
  • A file Studio wrote and nobody changed is replaced or removed as the change needs. A file whose first line names the import, but which Studio has no record of — in a fresh clone, say, where .courier/ is not checked in — is treated the same way when it is replaced; removing one waits for you, offered ticked.
  • A file you changed keeps your edits to endpoints when Studio updates it. A file Studio would replace whole, or remove, waits for you instead — and so does a file whose endpoints Studio cannot read as code, one saved mid-edit say, since it cannot keep your edits in it.
  • A file Studio did not write — one you added to the folder, or one its first line says Studio wrote for another import — is never removed, and replacing it waits for you.
  • A symbolic link or a folder where a file should be is never written through, replaced or removed, and nothing is written through a folder link that leads out of the project. A change that would write one of them is refused until you move it out of the way.

When a change holds steps that wait for you, Studio shows them with the rest of the change, each with its own checkbox, and takes only the steps you tick; a file left unticked stays as it is, and so does every file it imports: Studio never removes a file that a file staying in the import's folder imports, and the message after the change counts it among the files left as they were. An update, a change to endpoints and a removal are applied only as you previewed them: if a file changed in the meantime, Studio shows the change as it now stands instead. A write needs no preview, and asks only when a step waits for you.

Every change is applied all at once. Studio keeps a copy of each file it replaces or removes in .courier/backups/<change>/, and keeps the copies of the last 20 changes that made any. A change that Studio is stopped in the middle of is undone the next time it starts, file by file, except for a file changed since, which is left as it is. A change another Studio on the same project is still making is left to that Studio. Studio tries such an undo once: its copy of each file it could not restore is kept, whatever the limit, and Studio names those files at every start until you delete their folder in .courier/backups/. Studio undoes only what its own records name: a .courier folder that came with the project cannot make it touch other files. Removing an import takes it out of courier.config.ts too. Under the box Also remove … from courier.config.ts, ticked, the dialog shows the lines Studio removes: the import's sources entry with the import only that entry used, and its imports entry — an imports left empty goes, a sources left empty reads {}. Where the config takes its sources from a module of the project, the box reads Also remove … from lib/sources.ts and courier.config.ts, and the dialog shows the lines each file loses: the sources entry and its import come out of that module, the imports entry out of the config, both files changed or neither. Studio makes that change before any file goes, while the files are still there: it loads the config without the import and checks that the config no longer declares it nor imports any of the files that go, and only then removes them. A config — or that module — saved after the dialog was shown, or a change Studio could not make — the config would still import one of the files, say — removes nothing, and the dialog shows the removal again with the reason, the box unticked when the change failed. Untick the box to leave the config as it is; then, and whenever Studio cannot change the config — it does not load, it builds sources in a way Studio cannot change, or the browser is not on the machine Studio runs on — the dialog warns when the config imports one of the files that go, since the config stops loading until you remove that import, and reminds you to remove the import's entry under imports too, since Studio goes on checking the import for updates while the config declares it.

A request Studio refuses changes nothing. It is answered with { error } — 404 for an import Studio does not know, 400 for a body it cannot read — and, for the refusals below, with a code and, where there is one, the plan as it stands. A change that needs courier.config to load is refused as How Studio loads the project describes.

CodeStatusMeaning
NEEDS_CONFIRMATION409Some steps wait for you; send the request again with the plan's planDigest and the paths you allow in confirm.
PLAN_NOT_SEEN409An update, a change to endpoints or a removal quoted no plan; preview it first.
PLAN_STALE409The plan is not the one you saw: a file it rests on changed, or the import's declaration, the change asked for or the project's formatter configuration did.
CONFIG_STALE409A removal quoted, as configBaseHash, a change to courier.config.ts planned against the config as it was — and the module it takes its sources from, when the change writes that too: one of them was saved since the preview. Nothing was removed.
CONFIG_NOT_CHANGED409Studio could not take the import out of courier.config.ts — the config did not load without it, still imported a file the removal takes, or could not be written — and left it as it was. Nothing was removed.
TARGET_BLOCKED409A file the change writes is a symbolic link or not a file.
CONFIRMATION_NOT_IN_PLAN400confirm names a path the plan does not ask about.
DECLARATION_MISMATCH409imports declares the import in another folder, or with another type, validation, grouping, client or other generation option, than the wizard's options; the message names each one. An update and Manage generate as declared, so an import written otherwise would change layout at the next of them.
DIR_OVERLAP409Another import owns the folder, or one inside or around it.
NAME_TAKEN409sources declares another API under the import's name.
FILE_NOT_RECOGNIZED409A file Studio would add endpoints to no longer shows where its endpoints are.
ENDPOINT_OUTSIDE_PREFIX409An endpoint you add would call a URL the prefix its group file declares cannot reach, or Studio cannot read that prefix; add it by hand, or import the API again under the same name. An endpoint the file's prefix can reach is written relative to it, whatever prefix the specification gives the group.
IMPORTS_STATE_UNSAFE409Studio cannot read .courier/imports-state.json, or keep a copy of it, so saving it would lose the other imports' records.
COURIER_DIR_UNSAFE409.courier, or a folder Studio keeps in it, is a symbolic link or not a folder; Studio keeps nothing there.
INVALID_IMPORT400The output directory, the source name or a group's name is one Studio does not write to: outside the project, in .git, node_modules or .courier, or a name Windows treats differently.
PATH_OUTSIDE_PROJECT400A path leads out of the project.
SPEC_NOT_GENERATED400Studio could not generate code from the specification sent.
SPEC_TOO_LARGE413The specification is larger than 64 MB once parsed: Studio takes the parsed specification back when it generates, writes, updates or manages an import, and a request body may be at most 64 MB.
SPEC_UNREADABLE502The import's from answered with something that is not a specification Studio reads, such as a page.
SPEC_PIN_MISMATCH409The specification's hash is not the one the import's pinHash names.
IMPORT_TRANSFORM_FAILED400The import's transform threw, or returned something that is not a specification.

How Studio formats the files

Studio writes the files the way your project formats its code, and shows them that way before you apply the change:

  • Biome, when a biome.json or biome.jsonc stands in the project or a folder above it, or its package.json lists @biomejs/biome: Studio runs the project's own Biome on each file — its native program, without the Node launcher — with your configuration: the formatter and the import sorting. Lint rules do not change the code.
  • ESLint, when an ESLint configuration stands there — an eslint.config.* file, or for ESLint 8 an .eslintrc file or an eslintConfig key in package.json: Studio applies the fixes your rules offer, as eslint --fix does. A file your configuration ignores stays as it is.
  • Prettier, when a Prettier configuration stands there, or package.json lists prettier or has a prettier key: Studio runs it last, alone or after Biome when Biome's formatter is off, reading .editorconfig, .prettierignore and .gitignore as Prettier's command line does.
  • None of them: the files are written as Studio generates them.

Studio loads ESLint and Prettier once, from your project, in threads of their own — up to three — and keeps them between changes, so a file takes milliseconds: Petstore's eight files take about 20 ms once the tools are loaded, GitHub's 89 under a second. A configuration you change is read on the next change, and the threads go after five idle minutes. A file goes through the tools again until a pass changes nothing.

A file you edited is formatted whole when Studio merges a change into it, as your editor would format it when you save it; a courier.mocks.ts Studio creates is formatted the same way. The change says which tools ran, with their versions. When a tool fails on a file — an ESLint configuration with no TypeScript parser cannot read TypeScript, say — the other tools still run on it, and the change says why; a tool the project configures but did not install is named the same way. A file no tool formats is still correct code.

From the command line

courier-studio import imports a specification as Import API does — the same files, written into the same folder and recorded the same way — from a terminal, a script or a CI job, without Studio running:

bash
npx @noverax/courier-studio import https://petstore3.swagger.io/api/v3/openapi.json

It takes a URL, a file, or - for standard input, and names the import after the specification, as the wizard does, unless --name names it. It shows the plan first — every file it creates, updates, replaces or removes, and the lines that declare the import in courier.config.ts — and asks before it writes anything. Then it declares the import as Add to courier.config.ts does: the lines added, written like the rest of the file — in the module the config takes its sources from too, when it does — the config loaded, and every file put back as it was when it no longer loads.

  • --yes writes without asking, taking the plan as the wizard offers it: a step that waits for you — replacing a file you changed, say — is taken only when the wizard offers it ticked, or when --confirm names its path. Every other file stays as it is, and the command says how to allow it. Where no one can answer, as in a CI job, nothing is written without --yes.
  • --dry-run shows the plan and writes nothing.
  • --include and --exclude pick endpoints by tag, by path as the specification writes it, or by operationId — --include tag:pets,op:getUser — and the import's imports entry records them, so an update keeps the selection. Once the config declares the import, a selector other than the one it declares is refused: change it in the config.
  • --out, --validation, --group-by, --client, --no-types, --no-schemas, --no-comments and --auth-middleware are the wizard's options, with its defaults.
  • --no-declare writes the files only, and prints the lines to add to courier.config.ts — each under the file it goes in, when the config takes its sources from a module.
  • A URL is fetched by the wizard's rules — see Specifications on a private network.
  • The project needs a courier.config.ts: without one, the command prints the config to start from.

It exits 0 once the import is written and declared, or its plan shown with --dry-run, and 1 when it writes nothing or something fails.

From traffic, for an API with no specification

An API with no specification can be declared from the calls your app makes to it. A source the app defines in its own code, which courier.config.ts does not list, shows in the Explorer with what its traffic tells — and its endpoints' panels say they are known only by their traffic. Declare <source> from traffic reads an OpenAPI 3.1 document from the calls Studio recorded and opens Import API on it, as though it were pasted; from there it is imported as any specification is: pick the endpoints, choose the options, preview the files, write them, and the source is declared.

  • Routes. A segment every call shares is written as it is; one that differs between calls, or that was a variable's value, is a parameter named after that variable — /products/{id} — or param1 when no variable carried it. The segments every endpoint's paths start with are the API's base path.
  • Requests. A read's query is the keys its calls sent, required when every call sent them, typed by the variables they carried; a write's body is its variables less those in its path.
  • Answers. The answer's schema is merged from every answer that came whole with the status most calls got: a field is required when every answer held it, nullable when it came as null, of every type it came as; a string has a format — date-time, uuid, email — when every string there had it. Numbers stay numbers: a few answers cannot tell a price of 12 from an integer. No value goes into the document.
  • Names. An endpoint keeps the name and the group the app gave it, as its operation id and tag. An answer that is a list of objects names the schema of one of them after its endpoint — ProductsListItem for products.list — so the code has a type for one item rather than an unnamed one nested in the list.
  • How sure each part is. Every field, path parameter and query parameter read from traffic says so, and on how many calls it rests: Inferred from traffic: in 2 of 3 answers., Inferred from traffic: sent in all 4 calls. The generated code keeps it as the field's comment, so whoever reads the code sees which parts rest on one call and which on many.
  • What is left out. A stream or a channel, a call with no answer, and an answer kept only in part; an endpoint whose route and method another already took is named rather than written twice.

The document is only as complete as the traffic: an endpoint the app never called is not in it, and a field no answer held is not in its schema. Call what you want declared first — or declare it now and follow its contract as traffic shows more.

A source declared this way stays tied to its traffic. The APIs page says it was read from traffic, and Manage endpoints reads the calls Studio recorded again rather than asking for a specification: an endpoint the app has called since is there to add, and one the traffic no longer shows stays until you remove it.

An endpoint of a declared source can show up in traffic too, when the app calls one the declaration does not have; its panel says it was called, but not declared. For a source declared from its traffic — while the app still calls its own definition — the panel offers Add <endpoint> from traffic, which opens Manage endpoints on the source with that endpoint picked, and the change is shown before it is written. For any other source it means the app runs another version of it than the one Studio read — one built before a change to its definition, or a definition the config does not import — so restart the app, or point the config at the definition the app uses.

Specifications on a private network

Import API and the update checks fetch a specification by URL on your behalf, so Studio does not reach into a network you could not see from your own browser. It connects to public addresses, and to this machine when the URL you gave points there — a specification your own dev server serves — following redirects as long as every hop stays on this machine. This machine is its loopback addresses and every address of its own network interfaces, a global IPv6 address included; a public address your router sends back to this machine, by hairpin NAT, is on none of them, so Studio cannot tell it from another machine's. It reaches a host on a private network, or this machine once any hop has been elsewhere, only when studio.allowedSpecHosts in courier.config.ts names that host:

ts
// courier.config.ts
import { defineConfig } from "@noverax/courier/config"
import { api } from "./src/api"

export default defineConfig({
  sources: { api },
  studio: { allowedSpecHosts: ["gitlab.internal", "10.0.0.7:8080"] },
})
  • Studio reads the list only when a fetch would reach such a host, from courier.config as it is on disk at that moment: a host a save has just added or removed counts.
  • While courier.config does not load, no private host is reached, not even one the last config that loaded named: the request is refused with 409 CONFIG_NOT_LOADING, and with 503 while Studio is still loading the config. A project without a config is told to create one and name the host there.
  • List hosts literally. An entry is the host the URL names, in any case — gitlab.internal allows it on any port, 10.0.0.7:8080 on that port only — an IPv6 address may be written with or without brackets, and a name in other letters than ASCII as it is written. An entry written as a URL, with a path, or ending in a dot never names a host: the config does not load, and its error names the entry. A URL that names no port has its scheme's default, so gitlab.internal:443 allows https://gitlab.internal. A host computed from something Studio does not watch, such as an environment variable or a file the config reads, is taken as of the last load, and one computed in a module Node runs itself as of the last time Node ran it.
  • 0.0.0.0 and ::, which mean every interface, are never reached, even when listed.
  • Every hop is checked as it connects: a host name is resolved once, every address it resolves to must pass, and the connection goes to one of those; a redirect is followed only once its target passes the same check, up to five redirects.
  • Behind a corporate proxy, start Studio with NODE_USE_ENV_PROXY=1 and the proxy in HTTPS_PROXY: it fetches through the proxy, checking each host as this machine resolves it before the proxy is asked, and reaches this machine directly. A certificate authority of your own goes in NODE_EXTRA_CA_CERTS. Proxies & Certificates has the details.
  • A URL Studio refuses, such as one that is not http:// or https:// or that names a private host the list does not, is answered 400. A host Studio could not read is answered 502, with a message that says which and why: it could not be reached, did not answer or finish sending within 60 s, answered with an error status, redirected more than five times or to something that is not an http:// or https:// URL, sent a response that is not HTTP Studio can parse, or sent a body that was cut off, does not decompress or passes 64 MB. A check of every import reports the same message as that import's result.