Error while using Airtable import script

Hello. I tried the import script (Migration von Airtable Bases zu SeaTable - SeaTable)

  1. First try: It looked ok in the logs, however I noticed that I forgot one link. So I wanted to add it. However, I could see that no table whatsoever had been created (still only my empty table 1).

  2. Second try after adding my link => error in the log (and nothing done)

  3. I thought the fact that the link was from a table to itself might be an issue, so I removed and tried again => error

  4. Deleted the whole base and tried on a new one, without the suspicious link => error

  5. Deleted again, tried to import a different base, with a single table, without link => error

Here’s the error:

[2025-04-28 15:13:43] [INFO] Start adding tables and columns in SeaTable base
[2025-04-28 15:13:43] [INFO] Tables and columns added in SeaTable base
Traceback (most recent call last):
File "/scripts/index.py", line 91, in <module>
import_header()
File "/scripts/index.py", line 84, in import_header
convertor.convert_metadata()
File "/home/seatable/.local/lib/python3.11/site-packages/seatable_api/convert_airtable.py", line 530, in convert_metadata
self.add_helper_table()
File "/home/seatable/.local/lib/python3.11/site-packages/seatable_api/convert_airtable.py", line 719, in add_helper_table
self.add_table(table_name, columns)
File "/home/seatable/.local/lib/python3.11/site-packages/seatable_api/convert_airtable.py", line 847, in add_table
table = self.base.add_table(table_name, columns=columns)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/seatable/.local/lib/python3.11/site-packages/seatable_api/main.py", line 26, in wrapper
return func(obj, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/seatable/.local/lib/python3.11/site-packages/seatable_api/main.py", line 33, in wrapper
return getattr(new_obj, func.__name__)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/seatable/.local/lib/python3.11/site-packages/seatable_api/api_gateway.py", line 117, in add_table
return parse_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/seatable/.local/lib/python3.11/site-packages/seatable_api/utils.py", line 213, in parse_response
raise ConnectionError(response.status_code, response.text)
ConnectionError: [Errno 400] {"error_type":"table_exist","error_message":"table Columns to be migrated manually exist"}

Your SeaTable base contains a table that the script tries to create.

Please delete all tables in your SeaTable base and give the last remaining one an arbitrary name. Then rerun it.

Nope. Like I said:

  1. I don’t see any table created by the script.
  2. I tried deleting the whole base and doing it again in a fresh one.
    I also tried renaming the default “Table 1”, creating a second one and deleting the first one, none of them with names of tables in the base I want to import.

It looks more like the first run of the script created a hidden, for internal purpose, table. And weirdly that it’s shared across bases. Something left in the script runtime environment ? But it would be weird that the runtime env is shared, since on new bases it’s not the same script. If the table is named “Columns to be migrated manually” like the error suggests, this is clearly an internal thing, not from my data.

This is technically impossible because the script runs in a Docker container that is thrown away after execution.

This is a table created by the script. As the name suggests, this table lists the columns that you have to migrate manually, such as formula, lookup, count, rollup columns.

Should this table be visible in a tab ?
How is it that it already exists at the first run of the script on a brand new table ?
Can I do anything to test for its existence or make sure it’s deleted ?

(By the way I have no weird column type that would need manual migration)

Can you please post your script here? (Remove Airtable’s PAT and the base ID as well as SeaTable’s API token if specified explicitly.)

 ## Parameterize the script
  
# SeaTable - Destination
server_url = 'https://cloud.seatable.io'
api_token = 'token'
# Add an API token of the SeaTable base
# See https://seatable.io/docs/en/seatable-api/erzeugen-eines-api-tokens/
# for more information on how to create a SeaTable API token
  
# Airtable - Source
airtable_personal_access_token = 'token'
# Add a Personal Access Token (PAT)
# PATs are 82-character strings and begin with "pat" (e.g. 'pat544WlSOq6T4Fvv.5710af6611aedbf28493c38084163494e02b24f078cf2d62f07105982a82a64d')
# See https://support.airtable.com/docs/creating-personal-access-tokens/
# for more information on how to create a PAT in Airtable
 
airtable_base_id = 'appxxxx'      
# Add the Base ID of the Airtable base
# Base IDs are alphanumeric strings and begin with "app" (e.g. 'appRfA3qspH3EJUnV')
# See https://support.airtable.com/docs/finding-airtable-ids/
# for more information on where to find the id of an Airtable base
  
table_names = ['Bands']
# Add the names of all tables of the Airtable base, i.e. ['table 1', 'table 2']
# The names must be enclosed in '' and comma-separated
 
first_columns = [
   ('Bands', 'Name'),
]
# Specify the names of the first columns in every table of the Airtable base
# Use the format ('table_name', 'first_column_name'), i.e. ('table 1', 'ID')
# The table and column name must be enclosed in '' and comma-separated
 
links = [

]
# Specify the links between the tables in the Airtable base
# Use the format ('table_name', 'column_name', 'other_table_name'), i.e., ('table 1', 'link to table 2', 'table 2')
# The table and column names must be enclosed in '' and comma-separated
# If the Airtable base contains no link columns, just leave the brackets empty
 
from seatable_api.constants import ColumnTypes
excluded_column_types = [
    ColumnTypes.FILE
]
# Specify the column types which are to be excluded from the data import when running the script in import-rows mode (excluded column types are still created in import-header mode)
# Use the constants from https://developer.seatable.io/scripts/python/objects/constants/, i.e. ColumnTypes.FILE to exclude file columns
# The specified column types must be comma-separated
# If no column types are to be excluded, just leave the brackets empty
# ColumnTypes.LINK_FORMULA are always excluded
 
excluded_columns = []
# Specify the names of the columns which are to be excluded from the data import when running script in import-rows mode (excluded columns are still created in import-header mode)
# Use the format ('table_name', 'column_name'), i.e. ('table 1', 'column A')
# The table and column names must be enclosed in '' and comma-separated
# If no columns are to be excluded, just leave the brackets empty
 
mode = 'import-header'          
# Specify the run-mode of the script, two options: 'import-header' and 'import-rows'
# Run 'import-header' first to create the data structure in the SeaTable base
##

I didn’t touch the actual script part from the doc:

import sys
from seatable_api import Base, AirtableConvertor
 
def get_convertor():
    base = Base(api_token, server_url)
    base.auth()
    convertor = AirtableConvertor(
        airtable_api_key=airtable_personal_access_token,
        airtable_base_id=airtable_base_id,
        base=base,
        table_names=table_names,
        first_columns=first_columns,
        links=links,
    )
    return convertor
 
def import_header():
    convertor = get_convertor()
    convertor.convert_metadata()
 
def import_rows():
    convertor = get_convertor()
    convertor.convert_data()
 
if mode == 'import-header':
   import_header()
 
elif mode == 'import-rows':
   import_rows()
 
else:
   print('The mode is not properly specified.')
 
 
## End script
##

@rdb Am I doing anything wrong or is it an internal error ?

I guess you are doing something wrong.

Bildschirmaufzeichnungvom2025-05-0211-59-12-ezgif.com-video-to-gif-converter

Voilà the script I used. (I didn’t specify the API token explicity, but used the context object.)

import sys
from seatable_api import Base, AirtableConvertor, context

## Parameterize the script
  
# SeaTable - Destination
server_url = 'https://cloud.seatable.io'
api_token = context.api_token
  
# Airtable - Source
airtable_personal_access_token = 'A_82_character_Airtable_PAT'
airtable_base_id = 'appVhIqHWUQxXCQMt'      

  
table_names = ['Bands']

first_columns = [
   ('Bands', 'Name')
]
 
links = [
]
 
from seatable_api.constants import ColumnTypes
excluded_column_types = [
    ColumnTypes.FILE
]
 
excluded_columns = []
 
mode = 'import-header'          
 
  
## No more edits required beyond this row
  

def get_convertor():
    base = Base(api_token, server_url)
    base.auth()
    convertor = AirtableConvertor(
        airtable_api_key=airtable_personal_access_token,
        airtable_base_id=airtable_base_id,
        base=base,
        table_names=table_names,
        first_columns=first_columns,
        links=links,
    )
    return convertor
 
def import_header():
    convertor = get_convertor()
    convertor.convert_metadata()
 
def import_rows():
    convertor = get_convertor()
    convertor.convert_data()
 
if mode == 'import-header':
   import_header()
 
elif mode == 'import-rows':
   import_rows()
 
else:
   print('The mode is not properly specified.')
 
 
## End script
##

Thank you, it worked. The only difference being the use of the context token, I would suggest updating your doc to recommend using this.

I was using an API Token created long ago, and thought it was universal, but it is linked to a base. So running the script actually created the new tables in another database than the one it was running in.
Hence:

  • It worked the first time
  • But I couldn’t see any change
  • It could not work the following times
  • It could not work from any new base either since it was already created in the base linked to the token

Glad the problem could be solved.

I’d say the documentation is pretty clear on the role of API tokens:

It is. But I had created that token long ago and didn’t remember that.

Using the context is more user-friendly. This is what you did. And I have since looked at the scripting documentation, this is what is used everywhere in example. Just saying it would be better to also have it in this example since I’m guessing there should be a lot of people like me, who stumble on this page as their first interaction with your scripting.

The context object does not work when you run the script locally. Specifying the API-token is the option that always works (unless you use a token for the wrong base).

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.