IwiConnect API docs

Using the API

Errors

Failures come back in the same envelope as successes, with success: false and a message written for whoever reads the log. The status code tells you whether to retry, fix the request, or wake someone up.

Shape

Response · 400
{
  "success": false,
  "message": "Invalid input: respond_by_date must be on or before end_date",
  "status_code": 400,
  "response_object": null
}

Validation failures list every problem they found rather than stopping at the first, so one round trip is usually enough to fix a bad payload.

Permission checks are the exception. When a request is turned away before it reaches a handler, the body is a bare object with a single error string and no envelope around it. Read the status code first and treat the body as best effort.

Response · 403
{
  "error": "You do not have permission to perform this request on this project"
}

Status codes

CodeMeaningWhat to do
400The body or query failed validation.Fix the request. Retrying unchanged will fail again.
401Missing, malformed or revoked key.Check the header format and whether the key still exists. Do not retry.
402No tokens left.Someone needs to buy tokens in the portal. Queue the send and alert a human.
403The key lacks a scope, is not party to the project, or is an organisation calling an iwi-only action.Read the message. Scope problems need a new key, the others need a different call.
404Not found, or not visible to this key.Both cases look identical on purpose. Confirm the id and the team the key belongs to.
409Conflict with current state.Usually a double send or an invitation that was already accepted. Re-read the resource before deciding.
413Payload too large.Use the presigned upload flow instead of inlining the file.
429Rate limited.Back off and honour RateLimit-Reset.
500Something broke on our side.Retry with backoff. If it persists, send us the time and the request path.

The ones that surprise people

402 on send and respond

Projects are billed per submission, and an organisation's replies cost a token every second reply. A team with no tokens can create and edit drafts all day and only finds out when PUT /projects/{id}/send or PUT /projects/{id}/respond returns 402. Check current_credit_balance from GET /user before a batch run, and treat a 402 as work to resume rather than work to drop.

Iwi accounts are never charged, so they never see a 402.

Response · 402
{
  "success": false,
  "message": "No tokens available. Purchase tokens to submit this project.",
  "status_code": 402,
  "response_object": null
}

403 on complete

PUT /projects/{id}/complete is for iwi. A key belonging to an organisation is turned away with "Only iwi and super admins can perform this action", whichever side of the project it sits on. Organisations close projects instead. See who can do what.

403 from the ownership check

Project endpoints check that your team or iwi is party to the project before anything runs. A key that is on neither side gets 403, so nothing was created, changed or charged.

Actions that contradict a can_* flag are a different matter. The flags describe what the product allows, and most of the project endpoints do not re-check them, so a call like closing as an iwi on an organisation's project can succeed even though the portal would never offer it. Read the flags and respect them.

404 for things that exist

A project belonging to another team returns 404, not 403, because confirming the id exists would leak information. If you are certain the id is right, check which team your key belongs to with GET /user.

Retry policy

Node
const RETRYABLE = new Set([429, 500, 502, 503, 504]);

async function request(path, options = {}, attempt = 1) {
  const res = await fetch(`https://api.iwiconnect.com${path}`, options);

  if (RETRYABLE.has(res.status) && attempt < 4) {
    const reset = Number(res.headers.get("RateLimit-Reset") ?? 0);
    const waitMs = reset ? reset * 1000 : 2 ** attempt * 500;
    await new Promise((resolve) => setTimeout(resolve, waitMs));
    return request(path, options, attempt + 1);
  }

  const body = await res.json();
  if (!body.success) {
    throw new Error(`${body.status_code}: ${body.message}`);
  }
  return body.response_object;
}

Do not retry 400, 401, 402 or 403. None of them get better on their own, and a retry loop on a 402 turns a billing problem into a rate limit problem as well.

Logging

Log the status, the message and the path. Never log the key or the full Authorization header. When you raise a support ticket, include the timestamp with a timezone, the path and the message, and we can find the request.