Errors
The error envelope, what each status means, and when to retry.
Every error has the same shape, and the HTTP status tells you whose move it is. 4xx means the request needs to change before it can succeed. 429 and 5xx mean try the same request again later.
{
"error": {
"type": "invalid_request_error",
"code": "unknown_field",
"message": "requirements.directors is not a field in the SG PRIVATE_LIMITED_COMPANY schema (version 2026-09-01).",
"param": "requirements.directors",
"requestId": "req_01J9ZKM2QF7C4D"
}
}type: the broad class of error, one per status. Listed below.code: the specific reason, stable and safe to branch on. Each endpoint's reference page lists the codes it returns.message: a sentence for developers. It may change wording at any time; do not parse it or show it to end users as-is.param: the field or path the error is about, ornullwhen it is not about one input.requestId: quote it when you contact support. Log it with every failed request.
The types:
invalid_request_error:400authentication_error:401not_found_error:404conflict_error:409validation_error:422rate_limit_error:429api_error:500and other5xx
400 Bad request
The request is malformed or contains something the endpoint does not accept. Fix the request; retrying it unchanged will fail again.
{
"error": {
"type": "invalid_request_error",
"code": "unknown_field",
"message": "requirements.directors is not a field in the SG PRIVATE_LIMITED_COMPANY schema (version 2026-09-01).",
"param": "requirements.directors",
"requestId": "req_01J9ZKM2QF7C4D"
}
}unknown_field: the body has a key the endpoint does not accept. This is how PATCH/incorporations/{incorporationId}/intakerejects any key underrequirementsthat is not a field in the incorporation's schema, so a typo fails loudly instead of being dropped. The usual cause is sending data about people or companies through intake: that data belongs on persons and business entities.missing_file: an upload to POST/fileshad nofilepart.
A file larger than 20 MB is refused with status 413 and code file_too_large.
401 Unauthorized
The API key is missing, malformed, revoked, or not a GetStarted secret key.
{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "The API key provided is not valid.",
"param": null,
"requestId": "req_01J9ZKM5RG8D5E"
}
}Check that the header is Authorization: Bearer gs_test_… or Authorization: Bearer gs_live_…, that the key has not been rotated out, and that your deployment loaded the right environment variable. Do not retry until the key is fixed. See Authentication.
404 Not found
The object does not exist in this environment.
{
"error": {
"type": "not_found_error",
"code": "resource_not_found",
"message": "No incorporation with ID inc_01J9ZK7C3VYB5N exists in this environment.",
"param": "incorporationId",
"requestId": "req_01J9ZKM7SH9E6F"
}
}resource_not_found: no object with that ID. The most common cause is mixing environments: an ID created with ags_test_key is a404with ags_live_key, and the reverse.unsupported_jurisdiction: thejurisdictionandtypepair passed to GET/incorporations/requirementsor GET/incorporations/status-workflowis not in the catalog. Take both values from GET/incorporations/catalograther than hard-coding them.
409 Conflict
The request is valid but clashes with the current state of an object. Re-read the object, then decide.
{
"error": {
"type": "conflict_error",
"code": "incorporation_locked",
"message": "Incorporation inc_01J9ZK7C3VYB5N has been submitted. Only fields named in an open issue can be changed.",
"param": "requirements.businessActivity",
"requestId": "req_01J9ZKM9TJ1F7G"
}
}incorporation_locked: the incorporation has been submitted. Intake, participants and document slots are read-only from then on, except for whatever an open issue'spathpoints at. Check GET/incorporations/{incorporationId}/issuesto see what is editable.incorporation_cancelled: you tried to submit an incorporation that was cancelled. Start a new one.participant_exists: that person or entity is already a participant on this incorporation. Change its roles with PATCH/incorporations/{incorporationId}/participants/{participantId}instead of creating it again.duplicate_external_reference: another customer in this environment already has thatexternalReference. Look it up with GET/customersfiltered byexternalReference; you probably created it already.registry_field_locked: you tried to change a registry-issued field, such as theregistrationNumberof an entity formed through GetStarted.
422 Unprocessable
The request is well formed, but a value is not acceptable or the object is not ready for the action.
{
"error": {
"type": "validation_error",
"code": "validation_incomplete",
"message": "The incorporation cannot be submitted until every requirement is met.",
"param": null,
"requestId": "req_01J9ZKMB2K2G8H",
"missingRequirements": [
{
"path": "participants.par_01J9ZKE7LB2H6J.documents.PROOF_OF_ADDRESS",
"message": "A PROOF_OF_ADDRESS document is required for each DIRECTOR."
}
],
"invalidRequirements": [
{
"path": "requirements.companyNameOptions",
"code": "unique_items",
"message": "Each company name option must be different."
}
]
}
}validation_incomplete: returned by POST/incorporations/{incorporationId}/submitwhen the draft is not complete. The error carries the samemissingRequirementsandinvalidRequirementslists as GET/incorporations/{incorporationId}/validation, so you can route the user to each problem without a second call. See Requirements for the path grammar.invalid_value: a value fails type or format checks, such as a date that is notYYYY-MM-DDor a phone number that is not E.164.paramnames the field.type_unavailable: the type is in the catalog but hasavailable: false.role_not_allowed: the role is not in the schema'sparticipantRequirements, or the participant type is not allowed for it.document_kind_not_required: none of the participant's roles need thatdocumentKind.unsupported_file_type: the upload is not a PDF, JPEG or PNG.invalid_url: a webhook URL is not HTTPS or does not resolve.
429 and 5xx: retry
429 rate_limit_error means you are sending too many requests. 500 api_error and other 5xx responses mean something failed on our side. Both are safe to retry, and so are network timeouts.
- Retry with exponential backoff and jitter: wait about 1s, 2s, 4s, 8s, with some randomness, and give up after a handful of attempts.
- Send an
Idempotency-Keyon everyPOSTand reuse it across retries. If the first attempt actually succeeded before the connection dropped, the retry returns that result instead of creating a duplicate. See Idempotency. - Do not retry
4xxresponses other than429. They will fail the same way.
async function withRetry(doRequest, { attempts = 5 } = {}) {
for (let attempt = 0; ; attempt++) {
let res;
try {
res = await doRequest();
} catch (err) {
if (attempt >= attempts - 1) throw err; // network error
}
if (res && res.status !== 429 && res.status < 500) return res;
if (attempt >= attempts - 1) return res;
const delayMs = Math.min(30_000, 1000 * 2 ** attempt) * (0.5 + Math.random() / 2);
await new Promise((r) => setTimeout(r, delayMs));
}
}
// Generate the key once, outside the retry loop.
const idempotencyKey = crypto.randomUUID();
const res = await withRetry(() =>
fetch("https://api.getstarted.ae/v1/customers", {
method: "POST",
headers: {
Authorization: "Bearer gs_test_xxx",
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({ name: "Northwind Labs", email: "ops@northwindlabs.com" }),
}),
);If 5xx errors persist, note the requestId values and contact support.