ApiLinker

A universal bridge to connect, map, and automate data transfer between any two REST APIs
ApiLinker is an open-source Python package that simplifies the integration of REST APIs by providing a universal bridging solution. Built for developers, data engineers, and researchers who need to connect different systems without writing repetitive boilerplate code.
π Features
- π Universal Connectivity - Connect any two REST APIs with simple configuration
- πΊοΈ Powerful Mapping - Transform data between APIs with field mapping and path expressions
- π Data Transformation - Apply built-in or custom transformations to your data
- π Comprehensive Authentication - Support for API Key, Bearer Token, Basic Auth, and OAuth2
- π Flexible Configuration - Use YAML/JSON or configure programmatically in Python
- π Automated Scheduling - Run syncs once, on intervals, or using cron expressions
- π Data Validation - Validate data with schemas and custom rules
- π Plugin Architecture - Extend with custom connectors, transformers, and authentication methods
- π Pagination Handling - Automatic handling of paginated API responses
- π Error Recovery - Built-in retry logic and error handling
- π¦ Minimal Dependencies - Lightweight core with minimal external requirements
π Table of Contents
π Installation
Standard Installation
Install ApiLinker using pip (Python's package manager):
pip install apilinker
If you're using Windows, you might need to use:
py -m pip install apilinker
Make sure you have Python 3.8 or newer installed. To check your Python version:
python --version
py --version
Development Installation
To install from source (for contributing or customizing):
git clone https://github.com/kkartas/apilinker.git
cd apilinker
pip install -e ".[dev]"
pip install -e ".[docs]"
Verifying Installation
To verify ApiLinker is correctly installed, run:
python -c "import apilinker; print(apilinker.__version__)"
You should see the version number printed if installation was successful.
π― Beginner's Guide
New to API integration? Follow this step-by-step guide to get started with ApiLinker.
Step 1: Install ApiLinker
pip install apilinker
Step 2: Create Your First API Connection
Let's connect to a public API (Weather API) and print some data:
from apilinker import ApiLinker
linker = ApiLinker()
linker.add_source(
type="rest",
base_url="https://api.openweathermap.org/data/2.5",
endpoints={
"get_weather": {
"path": "/weather",
"method": "GET",
"params": {
"q": "London",
"appid": "YOUR_API_KEY"
}
}
}
)
weather_data = linker.fetch("get_weather")
print(f"Temperature: {weather_data['main']['temp']} K")
print(f"Conditions: {weather_data['weather'][0]['description']}")
Step 3: Save the Script and Run It
Save the above code as weather.py
and run it:
python weather.py
Step 4: Try a Data Transformation
Let's convert the temperature from Kelvin to Celsius:
def kelvin_to_celsius(kelvin_value):
return kelvin_value - 273.15
linker.mapper.register_transformer("kelvin_to_celsius", kelvin_to_celsius)
temp_kelvin = weather_data['main']['temp']
temp_celsius = linker.mapper.transform(temp_kelvin, "kelvin_to_celsius")
print(f"Temperature: {temp_celsius:.1f}Β°C")
Common Beginner Issues
- ImportError: Make sure ApiLinker is installed (
pip install apilinker
)
- API Key errors: Register for a free API key at the service you're using
- Connection errors: Check your internet connection and API endpoint URL
- TypeError: Make sure you're passing the correct data types to functions
π Quick Start
Using the CLI
Create a configuration file config.yaml
:
source:
type: rest
base_url: https://api.example.com/v1
auth:
type: bearer
token: ${SOURCE_API_TOKEN}
endpoints:
list_items:
path: /items
method: GET
params:
updated_since: "{{last_sync}}"
pagination:
data_path: data
next_page_path: meta.next_page
page_param: page
target:
type: rest
base_url: https://api.destination.com/v2
auth:
type: api_key
header: X-API-Key
key: ${TARGET_API_KEY}
endpoints:
create_item:
path: /items
method: POST
mapping:
- source: list_items
target: create_item
fields:
- source: id
target: external_id
- source: name
target: title
- source: description
target: body.content
- source: created_at
target: metadata.created
transform: iso_to_timestamp
- source: tags
target: labels
condition:
field: tags
operator: exists
transform: lowercase
schedule:
type: interval
minutes: 60
logging:
level: INFO
file: apilinker.log
Run a sync with:
apilinker sync --config config.yaml
Run a dry run to see what would happen without making changes:
apilinker sync --config config.yaml --dry-run
Run a scheduled sync based on the configuration:
apilinker run --config config.yaml
Using as a Python Library
from apilinker import ApiLinker
linker = ApiLinker(config_path="config.yaml")
linker = ApiLinker()
linker.add_source(
type="rest",
base_url="https://api.github.com",
auth={
"type": "bearer",
"token": "${GITHUB_TOKEN}"
},
endpoints={
"list_issues": {
"path": "/repos/owner/repo/issues",
"method": "GET",
"params": {"state": "all"}
}
}
)
linker.add_target(
type="rest",
base_url="https://gitlab.com/api/v4",
auth={
"type": "bearer",
"token": "${GITLAB_TOKEN}"
},
endpoints={
"create_issue": {
"path": "/projects/123/issues",
"method": "POST"
}
}
)
linker.add_mapping(
source="list_issues",
target="create_issue",
fields=[
{"source": "title", "target": "title"},
{"source": "body", "target": "description"}
]
)
result = linker.sync()
print(f"Synced {result.count} records")
linker.add_schedule(interval_minutes=60)
linker.start_scheduled_sync()
Step-by-Step Explanation:
- Import the library:
from apilinker import ApiLinker
- Create an instance:
linker = ApiLinker()
- Configure source API: Define where to get data from
- Configure target API: Define where to send data to
- Create mappings: Define how fields translate between APIs
- Run the sync: Either once or on a schedule
π§ Configuration
ApiLinker uses a YAML configuration format with these main sections:
Source and Target API Configuration
Both source
and target
sections follow the same format:
source:
type: rest
base_url: https://api.example.com/v1
auth:
endpoints:
timeout: 30
retry_count: 3
Authentication Methods
ApiLinker supports multiple authentication methods:
auth:
type: api_key
key: your_api_key
header: X-API-Key
auth:
type: bearer
token: your_token
auth:
type: basic
username: your_username
password: your_password
auth:
type: oauth2_client_credentials
client_id: your_client_id
client_secret: your_client_secret
token_url: https://auth.example.com/token
scope: read write
Field Mapping
Mappings define how data is transformed between source and target:
mapping:
- source: source_endpoint_name
target: target_endpoint_name
fields:
- source: id
target: external_id
- source: user.profile.name
target: user_name
- source: created_at
target: timestamp
transform: iso_to_timestamp
- source: description
target: summary
transform:
- strip
- lowercase
- source: status
target: active_status
condition:
field: status
operator: eq
value: active
π Data Transformations
ApiLinker provides built-in transformers for common operations:
iso_to_timestamp | Convert ISO date to Unix timestamp |
timestamp_to_iso | Convert Unix timestamp to ISO date |
lowercase | Convert string to lowercase |
uppercase | Convert string to uppercase |
strip | Remove whitespace from start/end |
to_string | Convert value to string |
to_int | Convert value to integer |
to_float | Convert value to float |
to_bool | Convert value to boolean |
default_empty_string | Return empty string if null |
default_zero | Return 0 if null |
none_if_empty | Return null if empty string |
You can also create custom transformers:
def phone_formatter(value):
"""Format phone numbers to E.164 format."""
if not value:
return None
digits = re.sub(r'\D', '', value)
if len(digits) == 10:
return f"+1{digits}"
return f"+{digits}"
linker.mapper.register_transformer("phone_formatter", phone_formatter)
π Examples
GitHub to GitLab Issue Migration
from apilinker import ApiLinker
linker = ApiLinker(
source_config={
"type": "rest",
"base_url": "https://api.github.com",
"auth": {"type": "bearer", "token": github_token},
"endpoints": {
"list_issues": {
"path": f"/repos/{owner}/{repo}/issues",
"method": "GET",
"params": {"state": "all"},
"headers": {"Accept": "application/vnd.github.v3+json"}
}
}
},
target_config={
"type": "rest",
"base_url": "https://gitlab.com/api/v4",
"auth": {"type": "bearer", "token": gitlab_token},
"endpoints": {
"create_issue": {
"path": f"/projects/{project_id}/issues",
"method": "POST"
}
}
}
)
linker.mapper.register_transformer(
"github_labels_to_gitlab",
lambda labels: [label["name"] for label in labels] if labels else []
)
linker.add_mapping(
source="list_issues",
target="create_issue",
fields=[
{"source": "title", "target": "title"},
{"source": "body", "target": "description"},
{"source": "labels", "target": "labels", "transform": "github_labels_to_gitlab"},
{"source": "state", "target": "state"}
]
)
result = linker.sync()
print(f"Migrated {result.count} issues from GitHub to GitLab")
More Examples
See the examples
directory for more use cases:
- Salesforce to HubSpot contact sync
- CSV file to REST API import
- Weather API data collection
- Custom plugin development
π» Common Use Cases with Examples
1. Sync Data Between Two APIs
This example shows how to sync customer data from CRM to a marketing platform:
from apilinker import ApiLinker
import os
linker = ApiLinker()
linker.add_source(
type="rest",
base_url="https://api.crm-platform.com/v2",
auth={
"type": "api_key",
"header": "X-API-Key",
"key": "${CRM_API_KEY}"
},
endpoints={
"get_customers": {
"path": "/customers",
"method": "GET",
"params": {"last_modified_after": "2023-01-01"}
}
}
)
linker.add_target(
type="rest",
base_url="https://api.marketing-platform.com/v1",
auth={
"type": "api_key",
"header": "Authorization",
"key": "${MARKETING_API_KEY}"
},
endpoints={
"create_contact": {
"path": "/contacts",
"method": "POST"
}
}
)
linker.add_mapping(
source="get_customers",
target="create_contact",
fields=[
{"source": "id", "target": "external_id"},
{"source": "first_name", "target": "firstName"},
{"source": "last_name", "target": "lastName"},
{"source": "email", "target": "emailAddress"},
{"source": "phone", "target": "phoneNumber", "transform": "format_phone"},
{"target": "source", "value": "CRM Import"}
]
)
def format_phone(phone):
if not phone:
return ""
digits = ''.join(c for c in phone if c.isdigit())
if len(digits) == 10:
return f"({digits[0:3]}) {digits[3:6]}-{digits[6:10]}"
return phone
linker.mapper.register_transformer("format_phone", format_phone)
result = linker.sync()
print(f"Synced {result.count} customers to marketing platform")
2. Scheduled Data Collection
This example collects weather data hourly and saves to a CSV file:
from apilinker import ApiLinker
import csv
import datetime
import time
import os
def save_weather_data(data, city):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
file_exists = os.path.isfile(f"{city}_weather.csv")
with open(f"{city}_weather.csv", mode='a', newline='') as file:
writer = csv.writer(file)
if not file_exists:
writer.writerow(["timestamp", "temperature", "humidity", "conditions"])
writer.writerow([
timestamp,
data['main']['temp'] - 273.15,
data['main']['humidity'],
data['weather'][0]['description']
])
print(f"Weather data saved for {city} at {timestamp}")
linker = ApiLinker()
linker.add_source(
type="rest",
base_url="https://api.openweathermap.org/data/2.5",
endpoints={
"get_london_weather": {
"path": "/weather",
"method": "GET",
"params": {
"q": "London,uk",
"appid": "YOUR_API_KEY"
}
},
"get_nyc_weather": {
"path": "/weather",
"method": "GET",
"params": {
"q": "New York,us",
"appid": "YOUR_API_KEY"
}
}
}
)
def collect_weather():
london_data = linker.fetch("get_london_weather")
nyc_data = linker.fetch("get_nyc_weather")
save_weather_data(london_data, "London")
save_weather_data(nyc_data, "NYC")
collect_weather()
linker.add_schedule(interval_minutes=60, callback=collect_weather)
linker.start_scheduled_sync()
try:
print("Weather data collection started. Press Ctrl+C to stop.")
while True:
time.sleep(60)
except KeyboardInterrupt:
print("Weather data collection stopped.")
π Extending ApiLinker
Creating Custom Plugins
ApiLinker can be extended through plugins. Here's how to create a custom transformer plugin:
from apilinker.core.plugins import TransformerPlugin
class SentimentAnalysisTransformer(TransformerPlugin):
"""A transformer plugin that analyzes text sentiment."""
plugin_name = "sentiment_analysis"
version = "1.0.0"
author = "Your Name"
def transform(self, value, **kwargs):
if not value or not isinstance(value, str):
return {"sentiment": "neutral", "score": 0.0}
positive_words = ["good", "great", "excellent"]
negative_words = ["bad", "poor", "terrible"]
text = value.lower()
positive_count = sum(1 for word in positive_words if word in text)
negative_count = sum(1 for word in negative_words if word in text)
total = positive_count + negative_count
score = 0.0 if total == 0 else (positive_count - negative_count) / total
return {
"sentiment": "positive" if score > 0 else "negative" if score < 0 else "neutral",
"score": score
}
Using Your Custom Plugin
After creating your plugin, you need to register it before using:
from apilinker import ApiLinker
from my_plugins import SentimentAnalysisTransformer
linker = ApiLinker()
linker.plugin_manager.register_plugin(SentimentAnalysisTransformer)
linker.add_mapping(
source="get_reviews",
target="save_analysis",
fields=[
{"source": "user_id", "target": "user_id"},
{"source": "review_text", "target": "sentiment_data", "transform": "sentiment_analysis"}
]
)
β Troubleshooting Guide
Installation Issues
Connection Issues
Mapping Issues
Common Code Examples
Handling API Rate Limits
from apilinker import ApiLinker
import time
linker = ApiLinker()
linker.add_source(
type="rest",
base_url="https://api.example.com",
retry={
"max_attempts": 5,
"delay_seconds": 2,
"backoff_factor": 2,
"status_codes": [429, 500, 502, 503, 504]
},
endpoints={
"get_data": {"path": "/data", "method": "GET"}
}
)
try:
data = linker.fetch("get_data")
print("Success!")
except Exception as e:
if "rate limit" in str(e).lower():
print("Rate limited, waiting and trying again...")
time.sleep(60)
data = linker.fetch("get_data")
else:
raise e
π Documentation
Documentation is available in the /docs
directory and will be hosted online soon.
Core Documentation
Quick Resources
Guides and Examples
Technical Documentation
- Architecture - System architecture and data flow diagrams
- Comparison - How ApiLinker compares to other integration tools
Step-by-Step Tutorials
Comprehensive API Reference
For developers who want to extend ApiLinker or understand its internals, we provide comprehensive API reference documentation that can be generated using Sphinx:
pip install sphinx sphinx-rtd-theme myst-parser
cd docs/sphinx_setup
sphinx-build -b html . _build/html
The generated documentation will be available in docs/sphinx_setup/_build/html/index.html
π Security Considerations
When working with APIs that require authentication, follow these security best practices:
-
Never hardcode credentials in your code or configuration files. Always use environment variables or secure credential stores.
-
API Key Storage: Use environment variables referenced in configuration with the ${ENV_VAR}
syntax.
auth:
type: api_key
header: X-API-Key
key: ${MY_API_KEY}
-
OAuth Security: For OAuth flows, ensure credentials are stored securely and token refresh is handled properly.
-
Credential Validation: ApiLinker performs validation checks on authentication configurations to prevent common security issues.
-
HTTPS Only: ApiLinker enforces HTTPS for production API endpoints by default. Override only in development environments with explicit configuration.
-
Rate Limiting: Built-in rate limiting prevents accidental API abuse that could lead to account suspension.
-
Audit Logging: Enable detailed logging for security-relevant events with:
logging:
level: INFO
security_audit: true
π€ Contributing
Contributions are welcome! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
)
- Install development dependencies (
pip install -e ".[dev]"
)
- Make your changes
- Run tests (
pytest
)
- Commit your changes (
git commit -m 'Add amazing feature'
)
- Push to the branch (
git push origin feature/amazing-feature
)
- Open a Pull Request
π Citation
If you use ApiLinker in your research, please cite:
@software{apilinker2025,
author = {Kartas, Kyriakos},
title = {ApiLinker: A Universal Bridge for REST API Integrations},
url = {https://github.com/kkartas/apilinker},
version = {0.2.0},
year = {2025},
doi = {10.21105/joss.12345}
}
π License
This project is licensed under the MIT License - see the LICENSE file for details.