> Toden documentation. For the full index fetch /llms.txt. Every page is available as Markdown by appending .md to its URL. Identity verification uses /api/kyc, Risk Intelligence uses /api/v1; they are separate contracts with different error shapes. Never invent an endpoint, field or enum value: read the API reference page for the operation first.

# Create remediation links for a grade

POST https://sandbox-api.toden.co/api/identity/address-triage/{jobId}/remediate
Content-Type: application/json

Creates one hosted verification link per customer in the chosen grade, each running the published workflow you name, so the customer can confirm their address. Up to 500 links are created per call; when `remaining` is above zero, call again to continue.

The call is safe to repeat: a row that already has a link is skipped and counted under `skipped.alreadyMinted`. Rows without your reference, rows for closed, suspended or rejected customers, and (unless you opt in) rows with no contact details are skipped and counted too. No entity is created by this call.

**Side effects.** Every created link fires the `entity.address_link_created` webhook. An email carrying the link is sent only to the recipients you name. The call is recorded in your audit log. Creating links is not billed; each verification is billed as usual when the customer completes it in production.

**Error codes**

| `error` | Status | Meaning |
|---|---|---|
| `invalid_request` | 400 | The body failed validation. `details` carries the field-level breakdown. |
| `grade_not_remediable` | 400 | Only the `confirmable_in_app` and `needs_presence` grades can be sent a link. |
| `insufficient_credits` | 402 | The balance cannot cover the request. |
| `triage_job_not_found` | 404 | No triage job has this identifier in this organisation and environment. |
| `workflow_not_available` | 422 | The workflow is not published, is suspended, or went offline during the run. |
| `workflow_subject_mismatch` | 422 | The workflow verifies businesses. Address remediation needs a workflow for individuals. |
| `secret_key_required` | 403 | A publishable key was used. This API accepts secret keys only. |
| `environment_mismatch` | 403 | The key belongs to an environment this host does not serve. |
| `rate_limited` | 429 | The request rate limit for this key was reached. Retry after a short wait. |
| `rate_limit_store_unavailable` | 503 | Rate limiting is temporarily unavailable. Retry shortly. |

A missing or unknown key returns `401` with a plain sentence in `error` rather than a code.

Requires a secret (`sk_`) key. A publishable key receives `403 secret_key_required`.

Reference: https://docs.toden.co/api-reference/verification/address-triage/remediate-address-triage

## Authentication

- `Authorization` header (bearer token, required) — Send the API key as a Bearer token. Each operation states whether a publishable key is enough or a secret key is required.

## Servers

- `https://sandbox-api.toden.co` (Sandbox, default)
- `https://api.toden.co` (Production)

## Request

### Path parameters

- `jobId` (string, required) — The `jobId` returned when the triage job was accepted.

### Body (application/json)

This endpoint expects an AddressTriageRemediateRequest.

- `grade` (enum, required) — The grade whose rows should receive a link. Only `confirmable_in_app` and `needs_presence` can be remediated.
  - Allowed values: `corroborated`, `confirmable_in_app`, `needs_presence`, `unresolvable`
- `workflowId` (string, required) — The public identifier of a published workflow for individuals that each link runs.
- `includeUnreachable` (boolean, optional) — Set to true to create links for rows with no contact details on file as well.
- `limit` (integer, optional) — The most links to create in this call. Defaults to 500.
- `recipients` (list of AddressTriageRemediateRequestRecipientsItems, optional) — Where to email each link. A row you do not name still gets a link and a webhook, but no email.

## Response

### 200

Success

- `minted` (integer, required) — How many links this call created.
- `emailed` (integer, required) — How many of those links were accepted for delivery by email.
- `skipped` (AddressTriageRemediationSkipped, required) — Rows in the grade that were deliberately passed over, by reason.
- `remaining` (integer, required) — Rows in the grade still waiting for a link. Call again to continue.
- `workflow` (AddressTriageRemediationWorkflow, required)
- `links` (list of AddressTriageRemediationLinksItems, required) — Every link this call created, so you can deliver them yourself. Treat each as a credential.

## Errors

### 400 Bad Request Error

The request body, query or path was invalid.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 401 Unauthorized Error

The Authorization header is missing, or the API key is unknown or revoked.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 402 Payment Required Error

The organisation does not have enough credit for this request.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 403 Forbidden Error

The key may not call this endpoint, or the organisation may not use this feature.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 404 Not Found Error

The resource does not exist for this organisation and environment.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 422 Unprocessable Entity Error

The request was understood but cannot be processed as sent.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 429 Too Many Requests Error

A rate limit was reached. Retry after a short wait.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 503 Service Unavailable Error

The service is temporarily unavailable.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

## Types

### AddressTriageRemediateRequestRecipientsItems

- `externalUserId` (string, required) — Your reference for the customer, as in the book.
- `email` (string, required) — The email address to send the link to.

### AddressTriageRemediationSkipped

Rows in the grade that were deliberately passed over, by reason.

- `alreadyMinted` (integer, required) — Rows that already have a link.
- `noReference` (integer, required) — Rows with no `externalUserId`.
- `unusable` (integer, required) — Rows whose customer is closed, suspended or rejected.
- `unreachable` (integer, required) — Rows with no contact details on file, skipped unless `includeUnreachable` is set.

### AddressTriageRemediationWorkflow

- `id` (string, required) — The public identifier of the workflow the links run.
- `name` (string, required) — The name of that workflow.

### AddressTriageRemediationLinksItems

- `externalUserId` (string, required) — Your reference for the customer.
- `url` (string, required) — The hosted verification link for that customer.
- `expiresAt` (datetime, required) — When the link stops working.
- `emailed` (boolean, required) — True only when an email carrying the link was accepted for delivery.

## Examples

**Request**

```json
{
  "grade": "confirmable_in_app",
  "workflowId": "wf_AbC123dEf456",
  "limit": 100,
  "recipients": [
    {
      "externalUserId": "user_42",
      "email": "ada@example.com"
    }
  ]
}
```

**Response**

```json
{
  "minted": 1,
  "emailed": 1,
  "skipped": {
    "alreadyMinted": 0,
    "noReference": 0,
    "unusable": 0,
    "unreachable": 0
  },
  "remaining": 0,
  "workflow": {
    "id": "wf_AbC123dEf456",
    "name": "Address confirmation"
  },
  "links": [
    {
      "externalUserId": "user_42",
      "url": "https://verify.toden.co/session/hs_test_01j9x2k4m8",
      "expiresAt": "2026-09-05T09:00:00.000Z",
      "emailed": true
    }
  ]
}
```

**SDK Code**

```python
import requests

url = "https://sandbox-api.toden.co/api/identity/address-triage/jobId/remediate"

payload = {
    "grade": "confirmable_in_app",
    "workflowId": "wf_AbC123dEf456",
    "limit": 100,
    "recipients": [
        {
            "externalUserId": "user_42",
            "email": "ada@example.com"
        }
    ]
}
headers = {
    "Authorization": "Bearer <apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://sandbox-api.toden.co/api/identity/address-triage/jobId/remediate';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <apiKey>', 'Content-Type': 'application/json'},
  body: '{"grade":"confirmable_in_app","workflowId":"wf_AbC123dEf456","limit":100,"recipients":[{"externalUserId":"user_42","email":"ada@example.com"}]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://sandbox-api.toden.co/api/identity/address-triage/jobId/remediate"

	payload := strings.NewReader("{\n  \"grade\": \"confirmable_in_app\",\n  \"workflowId\": \"wf_AbC123dEf456\",\n  \"limit\": 100,\n  \"recipients\": [\n    {\n      \"externalUserId\": \"user_42\",\n      \"email\": \"ada@example.com\"\n    }\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://sandbox-api.toden.co/api/identity/address-triage/jobId/remediate")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"grade\": \"confirmable_in_app\",\n  \"workflowId\": \"wf_AbC123dEf456\",\n  \"limit\": 100,\n  \"recipients\": [\n    {\n      \"externalUserId\": \"user_42\",\n      \"email\": \"ada@example.com\"\n    }\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://sandbox-api.toden.co/api/identity/address-triage/jobId/remediate")
  .header("Authorization", "Bearer <apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"grade\": \"confirmable_in_app\",\n  \"workflowId\": \"wf_AbC123dEf456\",\n  \"limit\": 100,\n  \"recipients\": [\n    {\n      \"externalUserId\": \"user_42\",\n      \"email\": \"ada@example.com\"\n    }\n  ]\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://sandbox-api.toden.co/api/identity/address-triage/jobId/remediate', [
  'body' => '{
  "grade": "confirmable_in_app",
  "workflowId": "wf_AbC123dEf456",
  "limit": 100,
  "recipients": [
    {
      "externalUserId": "user_42",
      "email": "ada@example.com"
    }
  ]
}',
  'headers' => [
    'Authorization' => 'Bearer <apiKey>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://sandbox-api.toden.co/api/identity/address-triage/jobId/remediate");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"grade\": \"confirmable_in_app\",\n  \"workflowId\": \"wf_AbC123dEf456\",\n  \"limit\": 100,\n  \"recipients\": [\n    {\n      \"externalUserId\": \"user_42\",\n      \"email\": \"ada@example.com\"\n    }\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "grade": "confirmable_in_app",
  "workflowId": "wf_AbC123dEf456",
  "limit": 100,
  "recipients": [
    [
      "externalUserId": "user_42",
      "email": "ada@example.com"
    ]
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://sandbox-api.toden.co/api/identity/address-triage/jobId/remediate")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```