# VCF to JSON Converter — vCard Contact Parser (`creatieve_naald/vcard-to-json`) Actor

Convert vCard 3.0/4.0 contact text to JSON for n8n, Make and agents. Keep multiple phones, emails and Apple labels. $0.02 per bundle, up to 500 contacts. Includes API and Python workflow examples.

- **URL**: https://apify.com/creatieve_naald/vcard-to-json.md
- **Developed by:** [Stefan Schellen](https://apify.com/creatieve_naald) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$20.00 / 1,000 parsed files

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## VCF to JSON Converter — vCard Contact Parser

Turn vCard 3.0/4.0 contact text into structured JSON for n8n, Make and AI agents. Keep **multiple phone numbers, multiple emails and Apple labels**, instead of flattening a contact to its first value.

**$0.02 per successful bundle — up to 500 contacts and 1 MiB.** No additional platform-usage charge. Rejected bundles produce no product event.

### Try it in one minute

1. Click **Try for free** and run the synthetic example using available Apify credit; otherwise the displayed price applies.
2. Open the dataset: one result contains a `contacts` array.
3. Use the API example below to connect your workflow. Review warnings before importing contacts anywhere.

### When to use it

- Prepare supported contact fields from a VCF export for your own import mapping.
- Keep repeated emails/phones and labels when passing contacts between workflow steps.
- Give an agent a deterministic parser with explicit limits and warnings.

**Why pay instead of using a free library?** Libraries such as vobject are a valid free option if you can host and maintain Python code. This Actor supplies a hosted API, bounded processing and a stable output envelope. It does not add contact enrichment or recover missing data.

**Not suitable for:** vCard 2.1, photos, lossless archival conversion or a ready-made CRM connector.

### Input and output

Send a JSON object with exactly one field, `vcard_text`. Example:

```json
{
  "vcard_text": "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Zoë Example\r\nN:Example;Zoë;;;\r\nitem1.TEL;TYPE=CELL:+31001\r\nitem1.X-ABLabel:iPhone\r\nTEL;TYPE=WORK:+31002\r\nEMAIL;TYPE=HOME:personal@example.test\r\nEMAIL;TYPE=WORK:work@example.test\r\nEND:VCARD\r\n"
}
```

This example produces one contact with two phones, two emails and an iPhone label. A complete response is shown below.

The result contains `schema_version`, `contact_count`, and `contacts`. Each contact has these field collections:

| Collection | Value |
|---|---|
| `names` | Structured family/given/additional/prefix/suffix |
| `formatted_names` | Full display name, without guessed splitting |
| `organizations` | Ordered organization components |
| `titles` | Job title text |
| `phones` | Original phone text or `tel:` URI |
| `emails` | Original email text |
| `addresses` | Structured postal-address components |

Each collection has `present` and `items`. Each item includes `value`, `types`, `group`, `labels`, and `parameters`. Repeated values are preserved, not deduplicated. Names and addresses may have repeated components represented as arrays. `completeness` reports `missing`, `empty`, or `present`; it is not a check of deliverability or identity.

One dataset item contains the entire result envelope. The same object is stored in key-value record `OUTPUT`. Split its `contacts` array in your workflow when you need one item per contact. This envelope makes one run easy to retrieve and avoids duplicated output on a run restart.

### Scope and limits

- Maximum **1 MiB UTF-8 input**, **500 contacts**, **256 properties/contact**, **8 KiB unfolded line**, **4 MiB compact JSON output**.
- Supports the documented contact fields in 3.0/4.0, folding, Unicode, escapes and Apple group labels. Not a full RFC conformance validator.
- Version 2.1 and legacy `CHARSET`/`ENCODING` on supported fields are rejected. Convert those files elsewhere first.
- Photos are omitted with `PHOTO_OMITTED`. Other fields such as NOTE, URL and birthday are omitted; their names appear in `unmapped_properties` with `UNMAPPED_PROPERTIES`.
- No photo/URL fetching, OCR, contact enrichment, CRM updates or inferred first/last names. This is not a lossless conversion of every vCard property.
- A malformed or over-limit bundle fails as a whole. Partial contacts are not returned or billed.

The parser worker has a 5-second wall timeout. On Linux it also has a 4-second CPU limit and 160 MiB address-space limit. The deployed Actor allocation is 256 MiB and 20 seconds total. Normal Linux cloud runs and build-time adversarial checks of memory, CPU and parent wall timeout passed.

### Errors

Failed runs have a fixed status code and, for rejected input, an `ERROR` record. Common codes: `UNSUPPORTED_VERSION`, `INPUT_LIMIT`, `LINE_LIMIT`, `CONTACT_LIMIT`, `PROPERTY_LIMIT`, `OUTPUT_LIMIT`, `UNTERMINATED_CARD`, `MALFORMED_PROPERTY`, `PARSE_ERROR`, `LEGACY_ENCODING_UNSUPPORTED`, `PROCESSING_TIMEOUT`. Values from the submitted contact are not included in those errors.

### Workflow integration

Use Apify's synchronous dataset endpoint:

`POST https://api.apify.com/v2/acts/creatieve_naald~vcard-to-json/run-sync-get-dataset-items?memory=256&timeout=20&maxTotalChargeUsd=0.02`

Authenticate using an `Authorization: Bearer …` header stored in your workflow's credentials. Do not put tokens in query strings, shared workflow exports or contact data. The JSON body is the input object. The HTTP result is an array containing one result envelope; iterate over its `contacts` array.

For **n8n**, use an HTTP Request node with POST, a stored Header Auth credential and JSON body `{{ {vcard_text: $json.vcard_text} }}`. Set a 30-second request timeout. Disable automatic retries that start a fresh run. Then use Split Out on `contacts`. The equivalent authenticated HTTP roundtrip has been tested. These n8n settings are a configuration recipe; a full n8n workflow import has not been verified.

For **Make**, use HTTP → Make a request, POST, the same URL and a stored Bearer credential. Build the body with JSON → Create JSON so line breaks and quotes are escaped correctly; map the text into `vcard_text`. Parse the response, then iterate the first envelope's `contacts`. This is a configuration recipe, not a tested Make blueprint.

For an **agent**, call the same HTTP endpoint with structured JSON input. Do not ask an LLM to reconstruct missing names. Inspect warnings before using the result.

A new API call is a new job and can be billed again. On a network timeout, inspect/retrieve the existing run before starting another. Automatic resumption of the same run uses its stored result and billing state.

### Price and data handling

Price: **$0.02 per successfully processed file/bundle**, independent of contact count within the limits. One `parsed-file` event; no automatic start/dataset charges or additional platform-usage charge. A rejected bundle produces no product event. The minimum permitted maximum charge per run is $0.02.

Apify receives input and stores the run's input/output and dataset according to account/platform settings. This is not a zero-retention service. The parser subprocess receives no account credentials and does not fetch external resources. Application status messages contain counts/codes only. Console retention is 7 days for unnamed storage/other runs, with the latest 10 runs retained for 4 months. An unauthenticated dataset request returned 403; unauthenticated INPUT, OUTPUT and JOB_STATE requests also returned 403. Anyone holding a valid signed sharing link may still access its target; do not share those links.

### Copyable Python workflow: VCF to contact rows

The example below calls Apify once and keeps every phone/email in arrays. Read a UTF-8 file locally; the Actor takes text, not a file URL. Set `APIFY_TOKEN` through your secret manager. Python 3 standard library only. The mapping and HTTP request construction were tested locally against saved acceptance output; this is not a verified n8n/Make import.

```python
"""Two copyable workflow adapters. No network calls on import."""
import json, os, urllib.request
from pathlib import Path

def call_actor(slug, field, text, price):
    url = f'https://api.apify.com/v2/acts/creatieve_naald~{slug}/run-sync-get-dataset-items?memory=256&timeout=20&maxTotalChargeUsd={price}'
    request = urllib.request.Request(url, data=json.dumps({field: text}).encode(), headers={'Authorization': 'Bearer '+os.environ['APIFY_TOKEN'], 'Content-Type': 'application/json'}, method='POST')
    # A timeout can mean the job ran. Never automatically start a replacement job.
    with urllib.request.urlopen(request, timeout=35) as response:
        rows = json.load(response)
    if not isinstance(rows,list) or len(rows)!=1 or rows[0].get('schema_version')!='1.0':
        raise ValueError('Unexpected response; inspect existing run before retrying')
    return rows[0]

def contact_rows(envelope):
    """One row/contact, keeping every phone and email in arrays."""
    return [{'name': [x['value'] for x in c['fields']['formatted_names']['items']],
             'phones': [x['value'] for x in c['fields']['phones']['items']],
             'emails': [x['value'] for x in c['fields']['emails']['items']],
             'warnings': c['warnings'], 'original': c} for c in envelope['contacts']]


result = call_actor('vcard-to-json', 'vcard_text',
                    Path('contacts.vcf').read_text(encoding='utf-8'), '0.02')
rows = contact_rows(result)
## Send rows to your chosen destination after checking warnings.
```

### Guide: preserve repeated contact values in your destination

Use the Python workflow above to produce one row per contact. Each row keeps `phones` and `emails` as arrays, plus `warnings` and the complete `original` contact. Review warnings before importing.

1. Map arrays into a destination that supports multiple values, or keep a linked child table.
2. Do not repeatedly overwrite one CRM phone field: the last assignment will erase earlier values even if parsing preserved them.
3. Retain `original` when you need Apple labels or other source fields later.
4. Test with synthetic contacts containing two phones, two emails and a label before using personal data.

Our offline fixture verifies those repeated values and the Apple label. Your CRM integration is not tested by this example. A timed-out API call may already have completed: inspect its existing run before retrying.

More bounded workflow tools: [Agent Tools portfolio](https://apify.com/creatieve_naald).

### Frequently asked questions

**Is the price per contact?** No: one successful bundle of 1–500 contacts costs $0.02 within the documented limits. 100 successful bundles cost $2, before any applicable taxes or account-specific discounts.

**Does it clean or verify addresses and emails?** No. It parses supported fields and reports omissions; it does not verify deliverability or guess missing values.

**Can it take a file URL?** No. Read your UTF-8 VCF file in your workflow and submit its text as `vcard_text`.

### Complete example response

The synchronous HTTP endpoint returns an array containing this envelope:

```json
[
  {
    "schema_version": "1.0",
    "contact_count": 1,
    "contacts": [
      {
        "index": 1,
        "version": "3.0",
        "fields": {
          "names": {
            "present": true,
            "items": [
              {
                "value": {
                  "family": "Example",
                  "given": "Zoë",
                  "additional": "",
                  "prefix": "",
                  "suffix": ""
                },
                "types": [],
                "group": null,
                "labels": [],
                "parameters": {}
              }
            ]
          },
          "formatted_names": {
            "present": true,
            "items": [
              {
                "value": "Zoë Example",
                "types": [],
                "group": null,
                "labels": [],
                "parameters": {}
              }
            ]
          },
          "organizations": {
            "present": false,
            "items": []
          },
          "titles": {
            "present": false,
            "items": []
          },
          "phones": {
            "present": true,
            "items": [
              {
                "value": "+31001",
                "types": [
                  "CELL"
                ],
                "group": "item1",
                "labels": [
                  "iPhone"
                ],
                "parameters": {
                  "TYPE": [
                    "CELL"
                  ]
                }
              },
              {
                "value": "+31002",
                "types": [
                  "WORK"
                ],
                "group": null,
                "labels": [],
                "parameters": {
                  "TYPE": [
                    "WORK"
                  ]
                }
              }
            ]
          },
          "emails": {
            "present": true,
            "items": [
              {
                "value": "personal@example.test",
                "types": [
                  "HOME"
                ],
                "group": null,
                "labels": [],
                "parameters": {
                  "TYPE": [
                    "HOME"
                  ]
                }
              },
              {
                "value": "work@example.test",
                "types": [
                  "WORK"
                ],
                "group": null,
                "labels": [],
                "parameters": {
                  "TYPE": [
                    "WORK"
                  ]
                }
              }
            ]
          },
          "addresses": {
            "present": false,
            "items": []
          }
        },
        "unmapped_properties": [],
        "warnings": [],
        "completeness": {
          "names": "present",
          "formatted_names": "present",
          "organizations": "missing",
          "titles": "missing",
          "phones": "present",
          "emails": "present",
          "addresses": "missing"
        }
      }
    ]
  }
]
```

### Support

Report reproducible problems within the documented scope through this Actor's Apify Issues tab. Include only synthetic examples and remove contact details, API tokens and signed links. Custom CRM integrations, enrichment and vCard 2.1 conversion are outside the scope.

# Actor input Schema

## `vcard_text` (type: `string`):

UTF-8 vCard 3.0/4.0, at most 1 MiB and 500 contacts. vCard 2.1 is not supported. Photos are omitted; unmapped fields are reported.

## Actor input object example

```json
{
  "vcard_text": "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Example Contact\r\nTEL;TYPE=CELL:+31001\r\nEMAIL:hello@example.test\r\nEND:VCARD\r\n"
}
```

# Actor output Schema

## `results` (type: `string`):

One result envelope containing contacts, schema version, and parsing warnings. Up to 500 contacts per bundle.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "vcard_text": `BEGIN:VCARD
VERSION:3.0
FN:Example Contact
TEL;TYPE=CELL:+31001
EMAIL:hello@example.test
END:VCARD`
};

// Run the Actor and wait for it to finish
const run = await client.actor("creatieve_naald/vcard-to-json").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "vcard_text": """BEGIN:VCARD\r
VERSION:3.0\r
FN:Example Contact\r
TEL;TYPE=CELL:+31001\r
EMAIL:hello@example.test\r
END:VCARD\r
""" }

# Run the Actor and wait for it to finish
run = client.actor("creatieve_naald/vcard-to-json").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "vcard_text": "BEGIN:VCARD\\r\\nVERSION:3.0\\r\\nFN:Example Contact\\r\\nTEL;TYPE=CELL:+31001\\r\\nEMAIL:hello@example.test\\r\\nEND:VCARD\\r\\n"
}' |
apify call creatieve_naald/vcard-to-json --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,creatieve_naald/vcard-to-json"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/eu2SdlePdhar4KlYx/builds/B6CvxDW0CXLGPBRbm/openapi.json
