Partner API documentation

Global Starlink — Partner API Guide

Integration guide for partner engineers

This guide covers everything you need to integrate with the Global Starlink Partner API: authenticating, submitting SIM activations in bulk, managing your KYC passport pool, and cancelling or rescheduling activations you've already submitted.

  • Base URL: https://api.gstarlink.com
  • Auth: OAuth 2.0 client credentials → short-lived bearer token. Submitting/changing needs write:orders; reading needs read:orders; retrieving eSIM QR / LPA needs read:esims.
  • Format: JSON request/response; errors are RFC 7807 application/problem+json with a machine-readable code.
  • Everything is scoped to your own account. You can only ever read or change your own passports and your own activations — another partner's records return 404.
  • You send the ICCID; we detect the carrier. You never specify the carrier — all products use the same activation flow, submitted and tracked the same way.
  • KYC is a shared responsibility. Some Australian carriers require a passport to register a SIM. You either include the passport on the line, or keep verified passports in your KYC pool for us to draw from automatically. If a KYC-required line has neither, it is still accepted but held as pending_no_passport until a passport is provided — so keep your pool stocked. See KYC passport pool.

Sandbox first. Your first API key is issued in sandbox mode. Run the full flow against it, confirm the outcomes look right, then we switch you to a production key. The base URL and request shapes are identical in both.


1. Authenticate

Exchange your client_id / client_secret for an access token.

curl -X POST https://api.gstarlink.com/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "scope": "write:orders read:orders"
  }'

Response (200):

{
  "access_token": "eyJhbGciOiJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "write:orders read:orders"
}
  • The token is a JWT, valid for 1 hour (expires_in: 3600). Cache it and reuse it; request a new one when it expires.
  • Scopes: submitting activations needs write:orders; reading activation status needs read:orders; retrieving eSIM QR / LPA needs read:esims. You can omit scope to receive every scope your key was granted (request "write:orders read:orders read:esims" to cover the full flow).
  • Send the token on every subsequent request as Authorization: Bearer <access_token>.

2. Submitting activations

2.1 Preflight — dry-run a batch (no changes)

Always call preflight before submitting. It runs the exact same checks as a real submission but writes nothing, so you can see, per card, whether it will be accepted and how KYC will be handled.

curl -X POST https://api.gstarlink.com/api/v1/activations/bulk/preflight \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "requested_activation_date": "2026-08-20",
    "lines": [
      { "iccid": "8961030000334077256" },
      { "iccid": "001OEVY6Y" }
    ]
  }'

Response (200):

{
  "outcomes": [
    { "line_no": 1, "iccid": "8961030000334077256", "status": "accepted", "kyc": "pool" },
    { "line_no": 2, "iccid": "001OEVY6Y",           "status": "accepted", "kyc": "pending_no_passport" }
  ],
  "pool_available": 12
}
  • pool_available — how many verified passports are currently available in your KYC pool.
  • One outcome per line — see Outcome reference for every field and value.

2.2 Submit the batch

Same request body as preflight, sent to the /bulk endpoint. This creates the activations.

curl -X POST https://api.gstarlink.com/api/v1/activations/bulk \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "requested_activation_date": "2026-08-20",
    "partner_reference": "ICC-2026-0088",
    "lines": [
      { "iccid": "8961030000334077256" },
      { "iccid": "001OEVY6Y" }
    ]
  }'

Response (201):

{
  "batch_id": "b1d2c3e4-...",
  "batch_number": "BA-20260820-01",
  "partner_reference": "ICC-2026-0088",
  "created": 2,
  "kyc_pending": 1,
  "outcomes": [
    { "line_no": 1, "iccid": "8961030000334077256", "status": "accepted", "kyc": "pool" },
    { "line_no": 2, "iccid": "001OEVY6Y",           "status": "accepted", "kyc": "pending_no_passport" }
  ]
}
  • batch_id / batch_number — keep these to reference the batch.
  • created — how many activation requests were created.
  • kyc_pending — how many were accepted but still need a passport (see KYC passport pool).
  • The response header Location points to the batch.

Request body

FieldTypeRequiredNotes
linesarrayyes1–200 items
lines[].iccidstringyesICCID or activation code, 8–22 letters/digits
requested_activation_datestringyes*YYYY-MM-DD, applies to the whole batch
lines[].activation_datestringno*YYYY-MM-DD, overrides the batch date for this one line
partner_referencestringnoyour own reference for this batch (max 200 chars); stored and echoed back in the response so you can reconcile against your internal batch/order id
notestringnomax 500 chars
urgentbooleannodefaults to false
KYC fields (per line)stringnosee KYC passport pool

Your own batch reference. batch_number (e.g. BA-20260820-01) is assigned by us; you can't set it. To tag a batch with your id, send partner_reference — we store it and return it in the response, so both sides can match the same batch.

* An activation date is required for every line. Supply it once for the whole batch via requested_activation_date, or per line via lines[].activation_date (which overrides the batch date). If a line has neither, it is rejected with reason missing_activation_date — a card can't be activated without a date. The date must be today or later (in Australia/Sydney time).

Card identifier. iccid accepts a numeric ICCID or an alphanumeric activation code (e.g. TeleChoice 001OEVY6Y) — any 8–22 letters/digits. Send exactly what's printed on the card. A value we don't recognise in your inventory comes back as not_found; a malformed value comes back as bad_format.

2.3 Check activation status

Poll by ICCID (list) or by request id (detail).

By ICCID:

curl "https://api.gstarlink.com/api/v1/activations?iccid=8961030000334077256" \
  -H "Authorization: Bearer <access_token>"
{
  "data": [
    {
      "id": "a1b2...",
      "iccid": "8961030000334077256",
      "carrier": "Optus",
      "activation_date": "2026-08-20",
      "customer_name": "Jane Doe",
      "status": "activated",
      "processing_status": "completed",
      "mobile_number": "04xxxxxxxx",
      "confirmation_number": "...",
      "created_at": "...",
      "updated_at": "..."
    }
  ],
  "count": 1
}

By request id: GET /api/v1/activations/{id} returns the same fields plus carrier_region, plan_name, note, error_type, error_message.

Status values you'll see (status):

StatusMeaning
activation_requestedreceived, queued for processing
pending / urgent_pendingawaiting processing
processingbeing worked
activated / completed / activelive — mobile_number is populated when available
failed / rejecteddid not activate (error_message explains)
cancelledcancelled
expiredactivation window passed

All products use the same activation flow — submit the request, then poll here until the card reaches a terminal status. How each carrier is processed on our side does not change anything you do.

Polling cadence. Activation is not instant — we process it (and for some carriers the SIM is activated by our operations team) after you submit, so there is a delay before the result is ready. Poll this endpoint on an interval (e.g. every 1–5 minutes) until each card reaches a terminal status (activated / failed / cancelled). When status is activated: physical-SIM results carry the mobile_number; eSIM results are retrieved as a QR / LPA in the next step.

2.4 Retrieve the eSIM QR / LPA (eSIM products)

For eSIM products the QR code and LPA string are generated when the card is activated (for TeleChoice, only after activation completes). Query directly by ICCID — needs the read:esims scope:

curl "https://api.gstarlink.com/api/v1/esims/{iccid}/status" \
  -H "Authorization: Bearer <access_token>"

Response (200):

{
  "iccid": "8961030000334077256",
  "status": "active",
  "activation_code": "LPA:1$smdp.gstarlink.com$K2-1A2B3C-4D5E6F",
  "qr_code_url": "https://api.gstarlink.com/api/v1/esims/8961030000334077256/qr",
  "smdp_address": "smdp.gstarlink.com",
  "activated_at": "2026-08-20T02:15:00Z",
  "expires_at": "2026-09-19T02:15:00Z",
  "product": { "name": "Telechoice 60GB", "sku": "eSIM-AU-TC-01", "validity_days": 30 }
}
  • activation_code — the LPA string to install the eSIM (format LPA:1$<smdp>$<code>). This is the value your customer's phone needs.
  • qr_code_url — a ready-to-render QR image encoding that same LPA.
  • smdp_address — the SM-DP+ server, if you generate your own QR.
  • Before activation, status is inactive and activation_code / qr_code_url are null — poll until status is active, then read the credentials.

One endpoint, by ICCID. GET /api/v1/esims/{iccid}/status returns both the live status and the QR / LPA once ready — so for eSIM you can poll this single endpoint by ICCID until status is active, no order id needed. (The activation-status endpoint in step 2.3 stays useful for the request lifecycle and, for physical SIMs, the mobile_number.)

To pull every eSIM in one order at once, GET /api/v1/orders/{order_id}/esims returns the same activation_code / qr_code_url for each card.

Prefer push over polling? Subscribe to webhooks and we notify your endpoint the moment an activation finishes — see 9. Webhooks. Webhooks and polling can be used together; the polling flow above always remains available as a fallback.

Outcome reference

Every line in a preflight or submit response is one outcome:

FieldTypeMeaning
line_nonumber1-based index matching your lines array
iccidstringthe card, echoed back
status"accepted" | "rejected"whether the line was taken
reasonstringrejected only — why (table below)
kycstringaccepted only — how KYC was handled (table below)

reason (rejected lines):

ReasonMeaning — what to do
not_your_simThis card isn't in your inventory. Check the number.
not_foundWe don't recognise this card at all. Check for typos.
already_activeThe card is already activated — nothing to do.
duplicate_in_flightAn activation for this card is already in progress.
bad_formatNot a valid identifier (must be 8–22 letters/digits).
bad_activation_dateThe activation date is invalid or in the past.
missing_activation_dateNo activation date supplied for this line or batch.
no_partner_linkOwnership couldn't be resolved — contact us.

kyc (accepted lines):

ValueMeaning
noneThis product doesn't require KYC.
inlineYou supplied the passport details on the line; we'll use them.
poolYou didn't supply a passport; we drew a verified one from your KYC pool.
pending_no_passportKYC is required but your pool was empty — accepted anyway, held until a passport is added.

3. KYC passport pool

Some Australian carriers require an identity (passport) to register a SIM. You have two options per line:

  1. Send the passport inline — include the fields below on the line. We use them as-is.
  2. Send nothing — we automatically draw a verified passport from your KYC pool (passports you've uploaded and we've confirmed as onshore in Australia).

If KYC is required and neither is available, the line is still accepted (never rejected) and held as pending_no_passport until a passport is provided.

Optional per-line KYC fields:

FieldFormat
first_namestring
last_namestring
date_of_birthYYYY-MM-DD
passport_nostring
passport_countrystring
passport_expiry_dateYYYY-MM-DD

A passport may be registered up to 4 times per carrier (each carrier counts separately). We manage that limit for you when drawing from the pool.

The KYC pool is your store of passports. When you submit a KYC-required activation without a passport on the line, we automatically draw a verified passport from your pool. Keeping the pool stocked means fewer lines held as pending_no_passport.

Imported passports start UNVERIFIED. Every passport you add via the API is stored with verification_status: "pending". Our operations team verifies it before the auto-draw will use it — importing here is not a substitute for our KYC review. Keep this in mind for timing: import ahead of when you need them.

3.1 Import passports

POST /api/v1/kyc/passports — add up to 200 passports in one call. Duplicates (same passport number already in your pool, or repeated within the request) are skipped.

curl -X POST https://api.gstarlink.com/api/v1/kyc/passports \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {
        "title": "Mr",
        "first_name": "Alice",
        "last_name": "Traveller",
        "date_of_birth": "1990-01-01",
        "passport_no": "X1234567",
        "passport_country": "GB",
        "passport_expiry_date": "2030-01-01"
      }
    ],
    "group_label": "2026-q3-batch"
  }'

Response (201):

{ "ok": true, "created": 1, "skipped_duplicates": 0, "verification_status": "pending" }
  • Required per record: first_name, last_name, passport_no. Others are optional.
  • Dates are literal calendar dates, YYYY-MM-DD. group_label is an optional tag for your own reconciliation.

3.2 List your pool

GET /api/v1/kyc/passports — returns the passports you submitted, plus a neutral status of received. Withdrawn records are excluded. Scope: read:orders.

curl https://api.gstarlink.com/api/v1/kyc/passports?limit=100 \
  -H "Authorization: Bearer <access_token>"
{ "records": [
    { "id": "…uuid…", "first_name": "Alice", "last_name": "Traveller",
      "passport_no": "X1234567", "passport_country": "GB",
      "passport_expiry_date": "2030-01-01", "group_label": "2026-q3-batch",
      "status": "received" }
  ], "count": 1 }

The response returns only the data you submitted. Our internal verification state and usage counts are never exposed. status is always received for a passport that is in your pool.

3.3 Edit a passport

PATCH /api/v1/kyc/passports/{id} — correct a passport's details. Send any subset of the editable fields.

curl -X PATCH https://api.gstarlink.com/api/v1/kyc/passports/<id> \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "passport_expiry_date": "2031-05-01" }'
  • Only allowed while the passport has never been used for an activation. Once it has been drawn for an activation it is locked — you get 409 Passport In Use and must contact support.
  • Changing an identity field (passport number, name, expiry) resets our verification — the passport must be re-verified before it can be drawn again.

3.4 Withdraw a passport

DELETE /api/v1/kyc/passports/{id} — remove an unused passport from your pool. This is a soft delete: the record is retained for compliance, never physically erased.

curl -X DELETE https://api.gstarlink.com/api/v1/kyc/passports/<id> \
  -H "Authorization: Bearer <access_token>"
{ "ok": true, "id": "…uuid…", "status": "withdrawn" }

As with editing, this is only allowed while the passport has never been used (else 409 Passport In Use).


4. Cancel & reschedule

The one rule that governs both. You may cancel or reschedule an activation only while it is still activation_requested — i.e. before our team has submitted it to the carrier. Once it moves past that (submitted / processing), it is locked and you get 409; from then on it must be handled by our operations team.

You can address an activation by its ICCID (recommended — you already have it) or by the activation id returned when you look it up. Both single and batch are supported.

4.1 Cancel — single

By ICCID: POST /api/v1/activations/by-iccid/{iccid}/cancel

curl -X POST https://api.gstarlink.com/api/v1/activations/by-iccid/8961030000334077256/cancel \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "customer changed their mind" }'
{ "ok": true, "iccid": "8961030000334077256", "activation_id": "…", "status": "cancelled" }

By activation id: POST /api/v1/activations/{id}/cancel (same body/response, keyed by id).

  • Cancelling frees the SIM: it returns to your inventory as inactive, ready to be submitted again (for the same or a new customer).
  • Not cancellable (already submitted to carrier) → 409. No open activation for that ICCID → 404.

4.2 Cancel — batch

POST /api/v1/activations/bulk/cancel — up to 200 per call. Provide iccids, or activation_ids, or both.

curl -X POST https://api.gstarlink.com/api/v1/activations/bulk/cancel \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "iccids": ["8961030000334077256", "8961030000334077257"], "reason": "batch recalled" }'
{ "cancelled": 1, "errors": 1, "outcomes": [
    { "iccid": "8961030000334077256", "status": "cancelled" },
    { "iccid": "8961030000334077257", "status": "error", "reason": "not_cancellable:processing" }
  ] }

Partial success is normal — each item reports its own outcome (cancelled, or error with a reason such as not_found or not_cancellable:<status>).

4.3 Reschedule — single

Change the activation date (and/or note). By ICCID: PATCH /api/v1/activations/by-iccid/{iccid}

curl -X PATCH https://api.gstarlink.com/api/v1/activations/by-iccid/8961030000334077256 \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "requested_activation_date": "2026-09-15" }'
{ "ok": true, "iccid": "8961030000334077256", "activation_id": "…" }

By activation id: PATCH /api/v1/activations/{id} (same body, keyed by id).

  • Only the activation date and note are editable — customer/KYC details are not. Same activation_requested-only rule (else 409).

4.4 Reschedule — batch

POST /api/v1/activations/bulk/reschedule — up to 200 items. Each item is keyed by iccid or activation_id, with the new date and/or note.

curl -X POST https://api.gstarlink.com/api/v1/activations/bulk/reschedule \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "items": [
        { "iccid": "8961030000334077256", "requested_activation_date": "2026-09-15" },
        { "iccid": "8961030000334077257", "requested_activation_date": "2026-09-20", "note": "hold for pickup" }
      ] }'
{ "updated": 2, "errors": 0, "outcomes": [
    { "iccid": "8961030000334077256", "status": "updated" },
    { "iccid": "8961030000334077257", "status": "updated" }
  ] }

5. Changing the customer on an activation

There is no endpoint to change the customer/KYC details on an existing activation. If the customer details are wrong, or the SIM is going to a different customer, the flow is:

  1. Cancel the activation (Cancel & reschedule) — this frees the SIM back to inactive.
  2. Submit a new activation for that SIM with the correct/new customer details (see Submitting activations).

Rescheduling (changing the date) keeps the same activation; changing the customer is a fresh submission.


6. Errors

All errors (except the token endpoint) use RFC 7807 application/problem+json. Branch on the machine-readable code (or the per-item reason in batch responses), not on the human text.

{
  "type": "https://docs.gstarlink.com/errors/validation",
  "title": "Validation Error",
  "status": 400,
  "detail": "lines: array must contain at least 1 element(s)",
  "errors": { "lines": ["array must contain at least 1 element(s)"] }
}
StatusWhen
400Malformed JSON, or request failed validation (errors lists the fields).
401Missing / invalid / expired token — re-authenticate.
403Your key lacks the required scope, or the account is suspended.
422Your partner account isn't fully provisioned — contact us.
429Rate limit hit — back off and retry after Retry-After.
500Unexpected error on our side — safe to retry.

The OAuth token endpoint uses the OAuth error shape instead:

{ "error": "invalid_client", "error_description": "Invalid client credentials" }

Common values: invalid_request, invalid_client, invalid_scope, unauthorized_client, rate_limited.

Cancel / reschedule / KYC pool error codes and reasons:

Code / reasonMeaning
not_cancellable:<status>Activation is past activation_requested (already with the carrier). Contact ops.
not_editable:<status>Reschedule refused for the same reason.
passport_in_usePassport already used for an activation; edit/withdraw is locked.
passport_conflictA passport with that number already exists in your pool.
not_found / 404No such record in your account (also returned for another partner's record — no existence is leaked).
429Rate limited — retry after a short back-off.

7. Rate limits

ScopeLimit
Activation endpoints (bulk, preflight, single, status)60 requests / minute per partner
Token endpoint20 requests / minute per IP
Failed logins5 / 15 minutes per IP

Activation responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; a 429 includes Retry-After (seconds). Prefer batched submissions (up to 200 cards per call) over many single calls.


  1. Get a token (cache for ~55 minutes).
  2. Preflight your batch → fix any rejected lines, confirm KYC handling.
  3. Submit the batch → store batch_id / batch_number.
  4. Poll status by ICCID every 1–5 min until each card reaches a terminal status (activated / failed / cancelled).
  5. Once activated: read the mobile_number (physical SIM), or fetch the eSIM QR / LPA by ICCID via GET /api/v1/esims/{iccid}/status (eSIM).
  6. Keep your KYC pool stocked — import passports ahead of time; allow for our verification step before they auto-draw.
  7. Need to move a date? Reschedule — same activation, cheapest change.
  8. Need to change the customer, or drop the order? Cancel (frees the SIM), then re-submit if needed.
  9. Prefer working by ICCID — every operation accepts it, so you never need to track our internal ids.

(Prefer push? Subscribe to webhooks (9. Webhooks) and steps 4–5 are replaced by a single push to your endpoint — the polling flow stays available as a fallback.)


9. Webhooks

Instead of polling, you can have us push the activation result to your own HTTPS endpoint the moment it's ready. To turn this on, send us (1) your HTTPS URL and (2) the events you want. We reply with a signing secret — keep it private; it's how you verify each delivery really came from us.

Events:

EventFires when
activation.activatedAn activation you submitted has gone live. Physical SIM carries the mobile_number; eSIM carries the QR / LPA.
activation.failedAn activation reached a genuine dead end (e.g. the carrier rejected it). Carries an error_message.

Delivery envelope — every webhook POST body looks like this; the event-specific fields are under data:

{
  "event": "activation.activated",
  "event_id": "evt_9f2c1a7b3d",
  "timestamp": "2026-08-20T02:15:00Z",
  "webhook_id": "wh_1a2b3c",
  "data": { }
}

activation.activated — physical SIM:

{
  "activation_id": "act_7b3d9f2c1a",
  "iccid": "8961030000334077256",
  "carrier": "TeleChoice",
  "status": "active",
  "activation_date": "2026-08-20",
  "mobile_number": "+61400000000",
  "esim": null,
  "activated_at": "2026-08-20T02:15:00Z"
}

activation.activated — eSIM (mobile_number is null; the QR / LPA is under esim):

{
  "activation_id": "act_7b3d9f2c1a",
  "iccid": "8961030000334077256",
  "carrier": "TeleChoice",
  "status": "active",
  "activation_date": "2026-08-20",
  "mobile_number": null,
  "esim": {
    "activation_code": "LPA:1$smdp.gstarlink.com$K2-1A2B3C-4D5E6F",
    "qr_code_url": "https://api.gstarlink.com/api/v1/esims/8961030000334077256/qr",
    "smdp_address": "smdp.gstarlink.com"
  },
  "activated_at": "2026-08-20T02:15:00Z"
}

activation.failed:

{
  "activation_id": "act_7b3d9f2c1a",
  "iccid": "8961030000334077256",
  "carrier": "TeleChoice",
  "status": "failed",
  "error_message": "Carrier rejected the identity document",
  "failed_at": "2026-08-20T02:15:00Z"
}

Treat deliveries as "latest wins" by activation_id. A card is sometimes marked activated a moment before its mobile number (or eSIM QR / LPA) is recorded on our side. When that happens you'll receive activation.activated with those fields null, then a second activation.activated for the same activation_id once they're filled in. Upsert on activation_id and take the newest payload — never assume the first delivery is final, and don't treat the pair as two different cards.

Verify every delivery. Each request carries a signature header:

X-Webhook-Event: activation.activated
X-Webhook-ID: evt_9f2c1a7b3d
X-Webhook-Signature: t=1755654900,v1=<hex-hmac>

Recompute the signature and compare before trusting the body:

  1. Read t (unix seconds) and v1 from X-Webhook-Signature.
  2. Compute HMAC-SHA256( secret, "<t>." + rawRequestBody ) as lowercase hex — note the literal . between the timestamp and the exact raw body bytes (sign before any JSON re-serialisation).
  3. Reject if it doesn't equal v1, or if t is more than 5 minutes from your clock (replay protection).
// Node.js
const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const [tPart, v1Part] = header.split(',');
  const t = tPart.split('=')[1];
  const expected = v1Part.split('=')[1];
  const mac = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const ok = crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(expected));
  return ok && Math.abs(Date.now() / 1000 - Number(t)) < 300;
}

Acknowledge & retries. Respond 2xx quickly to acknowledge — do your heavy work asynchronously. If we don't get a 2xx we retry with exponential backoff (up to 5 attempts). After repeated consecutive failures the subscription is auto-disabled and we'll contact you. Deliveries are at-least-once, so make your handler idempotent (dedupe on event_id for exact repeats, and reconcile on activation_id per the note above).


Appendix — single-card endpoint

If you ever need to submit one card on its own, POST /api/v1/activations takes a single iccid + activation_date (plus KYC fields, which become required for KYC carriers) and returns one activation object. The bulk endpoint handles a single card equally well (just one item in lines), so most integrations only need the bulk flow above.


Questions? Contact your Global Starlink integration contact.