> ## Documentation Index
> Fetch the complete documentation index at: https://chainpatrol.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Organization Assets

> Batch add multiple assets to your organization's allowlist. Each asset is automatically parsed and classified. Assets are normally approved as ALLOWED; an asset ChainPatrol currently has BLOCKED, and requests from integration organizations, go to manual review instead. Supports optional group assignment and an optional allowlist expiry, for the whole request or per asset.

## Overview

Batch add multiple assets to your organization's allowlist. Each asset will be automatically parsed, classified by type, and approved with `ALLOWED` status. This endpoint supports adding up to 1,000 assets per request with optional metadata like name, description, and group assignment.

## Quick Start

### Authentication

Include your API key in the `X-API-KEY` header:

```bash theme={null}
X-API-KEY: your_api_key_here
```

### Example Request

```typescript TypeScript theme={null}
const response = await fetch(
  "https://app.chainpatrol.io/api/v2/organization/assets",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": "YOUR_API_KEY_HERE",
    },
    body: JSON.stringify({
      assets: [
        {
          content: "https://example.com",
          name: "Main Website",
          description: "Our official website",
          groupId: 1,
        },
        {
          content: "0x1234567890abcdef1234567890abcdef12345678",
          name: "Treasury Wallet",
        },
        {
          content: "@ourcompany",
        },
      ],
    }),
  }
);

const data = await response.json();
console.log(data);
```

```javascript JavaScript theme={null}
const response = await fetch(
  "https://app.chainpatrol.io/api/v2/organization/assets",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": "YOUR_API_KEY_HERE",
    },
    body: JSON.stringify({
      assets: [
        {
          content: "https://example.com",
          name: "Main Website",
          description: "Our official website",
          groupId: 1,
        },
        {
          content: "0x1234567890abcdef1234567890abcdef12345678",
          name: "Treasury Wallet",
        },
        {
          content: "@ourcompany",
        },
      ],
    }),
  }
);

const data = await response.json();
console.log(data);
```

```python Python theme={null}
import requests

response = requests.post(
    "https://app.chainpatrol.io/api/v2/organization/assets",
    headers={
        "Content-Type": "application/json",
        "X-API-KEY": "YOUR_API_KEY_HERE",
    },
    json={
        "assets": [
            {
                "content": "https://example.com",
                "name": "Main Website",
                "description": "Our official website",
                "groupId": 1,
            },
            {
                "content": "0x1234567890abcdef1234567890abcdef12345678",
                "name": "Treasury Wallet",
            },
            {
                "content": "@ourcompany",
            },
        ]
    },
)

data = response.json()
print(data)
```

```bash cURL theme={null}
curl -X POST 'https://app.chainpatrol.io/api/v2/organization/assets' \
  -H 'X-API-KEY: YOUR_API_KEY_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
    "assets": [
      {
        "content": "https://example.com",
        "name": "Main Website",
        "description": "Our official website",
        "groupId": 1
      },
      {
        "content": "0x1234567890abcdef1234567890abcdef12345678",
        "name": "Treasury Wallet"
      },
      {
        "content": "@ourcompany"
      }
    ]
  }'
```

## Request Body

| Field | Type | Required | Description |
| - | - | - | - |
| assets | array | Yes | Array of assets to add (maximum 1,000 per request) |
| assets\[].content | string | Yes | Asset content: URL, blockchain address, social media handle, email, domain, etc. |
| assets\[].name | string | No | Display name for the asset. Useful for identifying assets in your dashboard. |
| assets\[].description | string | No | Description providing context about the asset's purpose or usage. |
| assets\[].groupId | number | No | Group ID to assign the asset to. Must be a valid group belonging to your organization. |
| assets\[].expiresAt | string | No | Allowlist expiry for this asset, as an ISO 8601 date-time in the future. Overrides the request's `expiresAt`. |
| expiresAt | string | No | Allowlist expiry for every asset in the request, as an ISO 8601 date-time in the future. See [Setting an expiry when adding](#setting-an-expiry-when-adding). |

### Setting an expiry when adding

Assets that should only stay for a while, such as a partner's site, can be given an [allowlist expiry](https://chainpatrol.com/docs/concepts/allowlist.md#expiry) in the same request. Set `expiresAt` on the request to apply one date to every asset, or on an asset to give it its own date. An asset's own date overrides the request's.

```json theme={null}
{
  "expiresAt": "2027-04-30T23:59:59Z",
  "assets": [
    { "content": "https://partner-one.example" },
    { "content": "https://partner-two.example", "expiresAt": "2026-12-31T23:59:59Z" }
  ]
}
```

* The expiry is only set on assets that were added. Assets that fail to add get none.
* Each date must be in the future. If any is not, the request returns `400` and nothing is added.
* To change an expiry later, use [Set Organization Asset Expiry](https://chainpatrol.com/docs/external-api/organization-assets-expiry.md).

### Supported Asset Types

The endpoint automatically detects and classifies the following asset types:

* **URL** - Full URLs with protocol (e.g., `https://example.com`)
* **ADDRESS** - Blockchain addresses (e.g., Ethereum, Bitcoin addresses)
* **PAGE** - Web page identifiers
* **DISCORD** - Discord server or channel identifiers
* **TELEGRAM** - Telegram channel or group identifiers
* **TWITTER** - Twitter/X handles (e.g., `@username`)
* **INSTAGRAM** - Instagram handles
* **MEDIUM** - Medium publication or user identifiers
* **GITHUB** - GitHub repository or user identifiers
* **YOUTUBE** - YouTube channel identifiers
* **LINKEDIN** - LinkedIn profile identifiers
* **TWITCH** - Twitch channel identifiers
* **TIKTOK** - TikTok handle identifiers
* **EMAIL** - Email addresses

## Response

### Success Response

```json theme={null}
{
  "results": [
    {
      "content": "https://example.com",
      "success": true,
      "id": 12345
    },
    {
      "content": "0x1234567890abcdef1234567890abcdef12345678",
      "success": true,
      "id": 12346
    },
    {
      "content": "@ourcompany",
      "success": true,
      "id": 12347
    }
  ],
  "successCount": 3,
  "errorCount": 0
}
```

### Partial Success Response

When some assets succeed and others fail:

```json theme={null}
{
  "results": [
    {
      "content": "https://example.com",
      "success": true,
      "id": 12345
    },
    {
      "content": "invalid-content",
      "success": false,
      "error": "Invalid asset format"
    },
    {
      "content": "https://existing.com",
      "success": false,
      "error": "Asset already exists in organization"
    }
  ],
  "successCount": 1,
  "errorCount": 2
}
```

### Response Fields

| Field | Type | Description |
| - | - | - |
| results | array | Array of result objects, one for each submitted asset |
| results\[].content | string | The asset content that was submitted |
| results\[].success | boolean | Whether the asset was successfully added |
| results\[].id | number | Asset ID (only present when success is true) |
| results\[].error | string | Error message (only present when success is false) |
| results\[].expiresAt | string \| null | The allowlist expiry saved for the asset, or `null` when none was set (only present when success is true) |
| successCount | number | Total number of assets successfully added |
| errorCount | number | Total number of assets that failed to be added |

## Error Responses

### 400 Bad Request

Returned when the request is malformed or exceeds limits:

```json theme={null}
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "Maximum 1000 assets allowed per request"
  }
}
```

### 401 Unauthorized

Returned when the API key is missing, invalid, or doesn't have organization access:

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "API key with organization access required"
  }
}
```

### 404 Not Found

Returned when a specified group ID doesn't exist:

```json theme={null}
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Group with ID 999 not found"
  }
}
```

## Common Error Messages

| Error Message | Cause | Resolution |
| - | - | - |
| Invalid asset format | Asset content format not recognized | Verify the asset format matches a supported type |
| Asset already exists in organization | Duplicate asset in your organization | Asset is already in your allowlist, no action needed |
| Maximum 1000 assets allowed per request | Request contains more than 1,000 assets | Split into multiple requests |
| Group with ID {id} not found | Invalid group ID | Use a valid group ID from your organization |
| Content is required | Missing `content` field | Provide the `content` field for each asset |
| Invalid group ID | Group ID format is invalid | Provide a valid numeric group ID |

## Best Practices

### Batch Operations

* **Maximize throughput**: Use the full 1,000 asset limit per request when bulk importing
* **Handle partial failures**: Always check both `successCount` and `errorCount` in responses
* **Retry failed assets**: Extract failed assets from results and retry with corrected data

### Asset Format

* **URLs**: Include protocol (`https://` or `http://`)
  * ✅ `https://example.com`
  * ❌ `example.com` (will be interpreted as domain, not URL)
* **Blockchain addresses**: Use full address format
  * ✅ `0x1234567890abcdef1234567890abcdef12345678`
* **Social handles**: Include the `@` symbol for Twitter/social platforms
  * ✅ `@username`
  * ✅ `username` (also works)

### Metadata

* **Names**: Use descriptive names for easier identification in your dashboard
* **Descriptions**: Add context about the asset's purpose, especially for addresses and URLs
* **Groups**: Organize assets by type, project, or purpose using groups

### Error Handling

* Check the `results` array for per-asset success/failure status
* Use `successCount` and `errorCount` for aggregate reporting
* Log failed assets with their error messages for debugging
* Implement retry logic for failed assets after correcting issues

## Use Cases

### Import from Spreadsheet

```typescript theme={null}
import * as fs from "fs";
import * as csv from "csv-parse/sync";

async function importFromCSV(filePath: string) {
  const fileContent = fs.readFileSync(filePath, "utf-8");
  const records = csv.parse(fileContent, {
    columns: true,
    skip_empty_lines: true,
  });

  // Process in batches of 1000
  for (let i = 0; i < records.length; i += 1000) {
    const batch = records.slice(i, i + 1000);
    const assets = batch.map((row) => ({
      content: row.content,
      name: row.name,
      description: row.description,
      groupId: row.groupId ? parseInt(row.groupId) : undefined,
    }));

    const response = await fetch(
      "https://app.chainpatrol.io/api/v2/organization/assets",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-API-KEY": process.env.API_KEY,
        },
        body: JSON.stringify({ assets }),
      }
    );

    const result = await response.json();
    console.log(
      `Batch ${i / 1000 + 1}: ${result.successCount} succeeded, ${
        result.errorCount
      } failed`
    );

    // Log failures
    result.results
      .filter((r) => !r.success)
      .forEach((r) => console.error(`Failed: ${r.content} - ${r.error}`));
  }
}
```

### Add Multiple Asset Types

```typescript theme={null}
const assets = [
  // URLs
  {
    content: "https://our-website.com",
    name: "Main Website",
    groupId: 1,
  },
  {
    content: "https://docs.our-website.com",
    name: "Documentation",
    groupId: 1,
  },
  // Blockchain addresses
  {
    content: "0x1234567890abcdef1234567890abcdef12345678",
    name: "Treasury Wallet",
    groupId: 2,
  },
  {
    content: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
    name: "Staking Contract",
    groupId: 2,
  },
  // Social media
  {
    content: "@ourcompany",
    name: "Official Twitter",
    groupId: 3,
  },
  {
    content: "ourcompany",
    name: "Discord Server",
    description: "Official Discord community",
    groupId: 3,
  },
];

const response = await fetch(
  "https://app.chainpatrol.io/api/v2/organization/assets",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": "YOUR_API_KEY_HERE",
    },
    body: JSON.stringify({ assets }),
  }
);
```

## Notes

* Assets are automatically parsed and classified by type
* All successfully added assets receive `ALLOWED` status
* Duplicate assets (already in your organization) will fail with an appropriate error
* The endpoint returns partial success - some assets may succeed while others fail
* Organization is automatically determined from your API key
* Group IDs must belong to your organization
* Asset validation happens during processing; invalid formats will be reported in the results


## OpenAPI

````yaml POST /organization/assets
openapi: 3.0.3
info:
  title: ChainPatrol External API - OpenAPI 3.0
  description: ChainPatrol External API documentation
  version: 2.0.0
servers:
  - url: https://app.chainpatrol.io/api/v2
security: []
tags:
  - name: asset
  - name: report
  - name: dark-web
externalDocs:
  url: https://chainpatrol.com/docs
paths:
  /organization/assets:
    post:
      tags:
        - organization
      summary: Add assets to organization allowlist
      description: >-
        Batch add multiple assets to your organization's allowlist. Each asset
        is automatically parsed and classified. Assets are normally approved as
        ALLOWED; an asset ChainPatrol currently has BLOCKED, and requests from
        integration organizations, go to manual review instead. Supports
        optional group assignment and an optional allowlist expiry, for the
        whole request or per asset.
      operationId: organizationAssetsAdd
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                assets:
                  type: array
                  items:
                    type: object
                    properties:
                      content:
                        type: string
                        description: Asset content (URL, address, handle, etc.)
                      name:
                        type: string
                        description: Optional display name for the asset
                      description:
                        type: string
                        description: Optional description for the asset
                      groupId:
                        type: integer
                        minimum: 0
                        exclusiveMinimum: true
                        description: Optional group ID to assign the asset to
                      expiresAt:
                        type: string
                        format: date-time
                        description: >-
                          Optional allowlist expiry for this asset, as an ISO
                          8601 date-time in the future. Overrides the request's
                          `expiresAt`.
                    required:
                      - content
                  minItems: 1
                  maxItems: 1000
                  description: Array of assets to add (max 1000 per request)
                expiresAt:
                  type: string
                  format: date-time
                  description: >-
                    Optional allowlist expiry for every asset in the request, as
                    an ISO 8601 date-time in the future (e.g.
                    `2027-04-30T23:59:59Z`). After it passes, the assets stay on
                    your organization for a 14-day grace period, then are
                    removed from it; their status does not change. An asset's
                    own `expiresAt` overrides this.
              required:
                - assets
              description: >-
                Add assets to organization allowlist


                Batch add multiple assets to your organization's allowlist. 

                Each asset will be automatically parsed and classified.

                Assets are normally auto-approved as ALLOWED. Two cases go to
                manual review

                instead: an asset ChainPatrol currently has BLOCKED, because
                allowlisting one

                is an unblock, and requests from integration organizations. The
                response still

                reports each asset as added; the status change follows once a
                reviewer

                approves it.

                Pass `expiresAt` to give the assets an allowlist expiry, for
                example for a partner's site.
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        content:
                          type: string
                          description: Asset content
                        success:
                          type: boolean
                          description: Whether the asset was added successfully
                        id:
                          type: number
                          description: Asset ID if successful
                        error:
                          type: string
                          description: Error message if failed
                        expiresAt:
                          type: string
                          nullable: true
                          description: >-
                            The allowlist expiry saved for the asset, or `null`
                            when none was set. Present only for added assets.
                      required:
                        - content
                        - success
                    description: Results for each asset
                  successCount:
                    type: number
                    description: Number of assets successfully added
                  errorCount:
                    type: number
                    description: Number of assets that failed to add
                required:
                  - results
                  - successCount
                  - errorCount
                description: Batch add operation results
        '400':
          description: Invalid input data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error.BAD_REQUEST'
        '401':
          description: Authorization not provided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error.UNAUTHORIZED'
        '403':
          description: Insufficient access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error.FORBIDDEN'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error.INTERNAL_SERVER_ERROR'
      security:
        - ApiKey: []
components:
  schemas:
    error.BAD_REQUEST:
      type: object
      properties:
        message:
          type: string
          description: The error message
          example: Invalid input data
        code:
          type: string
          description: The error code
          example: BAD_REQUEST
        issues:
          type: array
          items:
            type: object
            properties:
              message:
                type: string
            required:
              - message
          description: An array of issues that were responsible for the error
          example: []
      required:
        - message
        - code
      title: Invalid input data error (400)
      description: The error information
      example:
        code: BAD_REQUEST
        message: Invalid input data
        issues: []
    error.UNAUTHORIZED:
      type: object
      properties:
        message:
          type: string
          description: The error message
          example: Authorization not provided
        code:
          type: string
          description: The error code
          example: UNAUTHORIZED
        issues:
          type: array
          items:
            type: object
            properties:
              message:
                type: string
            required:
              - message
          description: An array of issues that were responsible for the error
          example: []
      required:
        - message
        - code
      title: Authorization not provided error (401)
      description: The error information
      example:
        code: UNAUTHORIZED
        message: Authorization not provided
        issues: []
    error.FORBIDDEN:
      type: object
      properties:
        message:
          type: string
          description: The error message
          example: Insufficient access
        code:
          type: string
          description: The error code
          example: FORBIDDEN
        issues:
          type: array
          items:
            type: object
            properties:
              message:
                type: string
            required:
              - message
          description: An array of issues that were responsible for the error
          example: []
      required:
        - message
        - code
      title: Insufficient access error (403)
      description: The error information
      example:
        code: FORBIDDEN
        message: Insufficient access
        issues: []
    error.INTERNAL_SERVER_ERROR:
      type: object
      properties:
        message:
          type: string
          description: The error message
          example: Internal server error
        code:
          type: string
          description: The error code
          example: INTERNAL_SERVER_ERROR
        issues:
          type: array
          items:
            type: object
            properties:
              message:
                type: string
            required:
              - message
          description: An array of issues that were responsible for the error
          example: []
      required:
        - message
        - code
      title: Internal server error error (500)
      description: The error information
      example:
        code: INTERNAL_SERVER_ERROR
        message: Internal server error
        issues: []
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-API-KEY
      description: >-
        Your API key. This is required by most endpoints to access our API
        programatically. Reach out to us at
        [support@chainpatrol.io](mailto:support@chainpatrol.io?subject=Re:%20API%20Key%20for%20SDK&body=Company:%20%0AName:%20%0APurpose:%20)
        to get an API key for your use.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.