[January 17, 2020: With all examples in the previous version of this answer I made a big mistake:
I had the macro \tableofaps write the beginning and the ending of the tabular-environment via\newaddtocontents/ \addtocontents.
This way the order in which the corresponding \newwritefile-/\@writefile-entries appear in the .aux-file and thus the order in which lines appear in the .lap-file will be wrong in case \tableofaps is not placed at the beginning of the document but somewehere behind a call to \actionpoint, e.g., in the middle or at the end of the document.
With this edit the beginning and the ending of the tabular-environment is not written to file at all. Instead \tableofaps locally patches \@input to wrap the call to \@@input (\@@input is the LaTeX 2e-name of the TeX-primitive \input) into \begin{tabular}...\end{tabular}.]
Using \@for or whatever for iterating on a list of tablecell-contents is interesting only in case you need flexibility regarding the amount of table-cells of a table-row.
If you always have exactly four table-cells, you can easily do without iteration/recursion/loops and just define macros that process four arguments:
\documentclass[a4paper]{article}
%\usepackage{hyperref}
\makeatletter
\newcounter{ap}
% The starred variant of \apref is always without hyperlink.
% The non-starred variant of \apref is without hyperlink only
% in case hyperref is not loaded.
\@ifdefinable\apref{%
\DeclareRobustCommand{\apref}{\@ifstar\apref@star\apref@nostar}%
}%
\@ifpackageloaded{hyperref}{%
\newcommand{\apref@nostar}[1]{\hyperref[{ap:#1}]{A\ref*{ap:#1}}}%
\newcommand{\apref@star}[1]{A\ref*{ap:#1}}%
}{%
\newcommand{\apref@nostar}[1]{A\ref{ap:#1}}%
\newcommand{\apref@star}[1]{A\ref{ap:#1}}%
}%
% What happens when we define an action point.
\newcommand\actionpoint[4]{%
\ifvmode\leavevmode\fi
\refstepcounter{ap}%
\apref*{#1}%
\label{ap:#1}%
\addtocontents{lap}{%
\string\MakeMyDayByMakingMyTableline{\string\apref{#1}}{#2}{#3}{#4}\string\protected@file@percent
}%
}
\newcommand\MakeMyDayByMakingMyTableline[4]{\hline#1\\}%
% The list of action points.
\newcommand\tableofaps{%
\begingroup
% Patch \@input to wrap the call to \@@input (which is the LaTeX-name
% of the TeX-primitive \input) into \begin{tabular}..\end{tabular}%
\patch@input
\@starttoc{lap}%
\endgroup
}
\newcommand\patch@input{%
\let\saved@input=\@input
\def\@input##1{%
\let\@input=\saved@input
\IfFileExists{##1}{%
\begin{tabular}{|c|c|c|c|}%
\hline
heading1&heading2&heading3&heading4\\
\@@input\@filef@und
\hline
\end{tabular}%
}{\typeout{No file ##1.}}%
}%
}%
% Just to make sure it is not already defined...
\newcommand\saved@input{}%
\makeatother
\begin{document}
\tableofaps
\actionpoint{a1}{b1}{c1}{d1}
\actionpoint{a2}{b2}{c2}{d2}
\end{document}
Here the resulting .aux-file:
\relax
\newlabel{ap:a1}{{1}{1}}
\@writefile{lap}{\MakeMyDayByMakingMyTableline{\apref{a1}}{b1}{c1}{d1}\protected@file@percent}
\newlabel{ap:a2}{{2}{1}}
\@writefile{lap}{\MakeMyDayByMakingMyTableline{\apref{a2}}{b2}{c2}{d2}\protected@file@percent}
Here the resulting .lap-file:
\MakeMyDayByMakingMyTableline {\apref {a1}}{b1}{c1}{d1}%
\MakeMyDayByMakingMyTableline {\apref {a2}}{b2}{c2}{d2}%
Here a picture of the resulting .pdf-output-file:

The sequence \if@tempswa{ & }\else\@tempswatrue\fi\next is a problem:
In case the condition is true, the braces that surround & will not be stripped off.
\if../\else/\fi-matching is independent from group matching and from brace-matching and from the &-matching of the align-mechanisms that underlie tabular-environments.
The braces surrounding the & will disturb the align-mechanisms that underlie tabular-environments.
& being wrapped into \if..\else..\fi will within table-environments disturb \if..\else..\fi-matching.
Besides this things that form the content of a table-cell will be processed within a group/local scope.
Thus \next after & will not end up in the local scope of that table-cell wherein \@for defined it, and therefore will be undefined at the time when LaTeX attempts to carry it out.
You can hide & within a macro and use some \expandafter-trickery for having LaTeX carry out \next before closing the local scope of the current table-cell.
With \addtocontents in some situations I prefer \string to \protect in order to avoid the writing of a trailing space behind the characters that form the name of the control-word-token that is to be written unexpanded.
Performing as few changes as possible to your code, you can get a table by omitting these braces, doing some \expandafter-trickery and having \tableofaps locally patch \@input to wrap \@@input into commands for beginning and ending some tabular-environment:
\documentclass[a4paper]{article}
%\usepackage{hyperref}
\makeatletter
\newcounter{ap}
% The starred variant of \apref is always without hyperlink.
% The non-starred variant of \apref is without hyperlink only
% in case hyperref is not loaded.
\@ifdefinable\apref{%
\DeclareRobustCommand{\apref}{\@ifstar\apref@star\apref@nostar}%
}%
\@ifpackageloaded{hyperref}{%
\newcommand{\apref@nostar}[1]{\hyperref[{ap:#1}]{A\ref*{ap:#1}}}%
\newcommand{\apref@star}[1]{A\ref*{ap:#1}}%
}{%
\newcommand{\apref@nostar}[1]{A\ref{ap:#1}}%
\newcommand{\apref@star}[1]{A\ref{ap:#1}}%
}%
% \ap is not used anywhere, therefore it is commented out:
%\newcommand{\ap}[1]{\refstepcounter{ap}\label{ap:#1}\apref{#1}}
\long\def\newwritefile#1#2{%
\@ifundefined{tf@#1}\relax
{%
\add@percent@to@temptokena
\@empty#2\protected@file@percent
\add@percent@to@temptokena
\immediate\write\csname tf@#1\endcsname{\the\@temptokena}%
}%
}
\def\@newstarttoc#1{%
\begingroup
\makeatletter
\@input{\jobname.#1}%
\if@filesw
\expandafter\newwrite\csname tf@#1\endcsname
\immediate\openout \csname tf@#1\endcsname \jobname.#1\relax
\fi
\@nobreakfalse
\endgroup}
% passes argument to write file
\def\newaddcontentsline#1#2{%
\newaddtocontents{#1}{\string\newcontentsline{#2}%%%%%%%%% braces now surround #2.
\string\protected@file@percent}}
% basically, it writes to file
\long\def\newaddtocontents#1#2{%
\protected@write\@auxout
{\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
{\string\newwritefile{#1}{#2}}}%
% Rendering of the line read in the file.
\def\newcontentsline#1{\shortcut{#1}\par}
\def\AndHiddenFomAlignMechanisms{&}
\newcommand{\shortcut}[1]{%
\hline
\global\@tempswatrue
\@for\next:=#1\do{%
\if@tempswa
\global\@tempswafalse
\else
\expandafter\expandafter\expandafter\AndHiddenFomAlignMechanisms
\fi
\next
}%
\\% <- End the table-row.
}
% What happens when we define an action point.
\def\actionpoint#1#2#3#4{%
\ifvmode\leavevmode\fi
\refstepcounter{ap}%
\apref*{#1}%
\label{ap:#1}%
%%% One pair of braces removed because that will now be added by
%%% \newaddcontentsline. \newaddcontentsline is safer this way.
\newaddcontentsline{lap}{{\string\apref{#1}},{#2},{#3},{#4}}%
}
% The list of action points.
\newcommand\tableofaps{%
\begingroup
% Patch \@input to wrap the call to \@@input (which is the LaTeX-name
% of the TeX-primitive \input) into \begin{tabular}..\end{tabular}%
\patch@input
\@newstarttoc{lap}%
\endgroup
}
\newcommand\patch@input{%
\let\saved@input=\@input
\def\@input##1{%
\let\@input=\saved@input
\IfFileExists{##1}{%
\begin{tabular}{|c|c|c|c|}%
\hline
heading1&heading2&heading3&heading4\\
\@@input\@filef@und
\hline
\end{tabular}%
}{\typeout{No file ##1.}}%
}%
}%
% Just to make sure it is not already defined...
\newcommand\saved@input{}%
\makeatother
\begin{document}
\tableofaps
\actionpoint{a1}{b1}{c1}{d1}
\actionpoint{a2}{b2}{c2}{d2}
\end{document}
Here the resulting .aux-file:
\relax
\newlabel{ap:a1}{{1}{1}}
\newwritefile{lap}{\newcontentsline{{\apref{a1}},{b1},{c1},{d1}}\protected@file@percent}
\newlabel{ap:a2}{{2}{1}}
\newwritefile{lap}{\newcontentsline{{\apref{a2}},{b2},{c2},{d2}}\protected@file@percent}
Here the resulting .lap-file:
\newcontentsline {{\apref {a1}},{b1},{c1},{d1}}%
\newcontentsline {{\apref {a2}},{b2},{c2},{d2}}%
Here a picture of the resulting .pdf-output-file:

As I like expansion-trickery I'd prefer a full expandable \romannumeral0-expansion-based tail-recursive loop for recursively iterating the list of cell-contents and hereby accumulating the tokens that form a table-row.
I think \romannumeral0-expansion needs some explanation.
Usually \romannumeral⟨number⟩ is applied for obtaining the representation of a number in roman notation.
Due to the subtle ways in which \romannumeral works, the sequence \romannumeral0 can nicely be (ab)used for triggering expansion without the need of writing a bunch of \expandafter:
The sequence \ronmannumeral0 triggers searching for more digits that belong to the digit-sequence that forms the ⟨number⟩ or for something that terminates the digit-sequence that forms the ⟨number⟩
During that search expansion of expandable things is triggered.
If a space-token is found, that space is discarded silently and terminates the process of searching for more digits.
If the digits gathered form a non-positive number, e.g., the number 0, \romannumeral will just silently swallow that number without delivering any token in return.
Therefore \romannumeral's expansion of things while searching for more digits or a terminator of the digit-sequence can nicely be abused for triggering a lot of expansion-work and flipping around of macro-arguments as long as it is ensured that after all that expansion-work the first things \romannumeral "finds" in the token-stream form a non-positive number, as is the case, e.g., with the sequence 0⟨space token⟩.
I suggest providing as argument to LaTeX's \addtocontents a sequence of tokens which consists of
- the call to a macro
\FormTableRowFromNonDelimitedArgList.
- a non-delimited/brace-nested argument which in turn consists of a list of non-delimited/brace-nested arguments whereof each holds the content of one of the table row's table cells.
Henceforth this argument is called the "list of tablecell-contents".
These things will end up in the .lap-file.
When the .lap-file is processed, the calls to \FormTableRowFromNonDelimitedArgList with their lists of tablecell-contents appended will be carried out.
At this stage \FormTableRowFromNonDelimitedArgList can initiate a full expandable \romannumeral0-expansion-driven tail-recursive loop for iterating on the list of tablecell-contents for accumulating the tokens for a table-row:
\FormTableRowFromNonDelimitedArgList just prepends to the list of tablecell-contents a token-sequence which consists of \romannumeral0\FormTableRowFromNonDelimitedArgListLoop⟨some more arguments⟩.
\FormTableRowFromNonDelimitedArgListLoop in turn processes some arguments:
- The tokens of the table-row accumulated so far.
- The remaining list of tablecell-contents.
- Tokens that shall precede each table-row. Something like
\hline.
- Tokens that shall trail each table-row. Something like
\\.
- Separator between table-cells. Something like
&.
\FormTableRowFromNonDelimitedArgListLoop is to process these arguments as follows:
If the remaining list of tablecell-contents is empty:
Terminate \romannumeral0-expansion by delivering a space (yielding that \romannumeral finds a non-positive number...) and the tokens of the table-row accumulated so far and hereby—in case the argument holding the tokens of the table-row accumulated so far is not empty—prepend to the tokens of the table-row accumulated so far the tokens from the argument for \hline and append to the tokens of the table-row accumulated so far the tokens from the argument for \\.
If the remaining list of tablecell-contents is not empty:
Call the tail-recusrsive loop-macro again, with its arguments modified as follows:
In case the argument holding the tokens of the table-row accumulated so far is not empty, have the tokens from the argument for & appended to it.
Have the first element of the remaining list of tablecell-contents appended to it.
Have the first element removed from the argument holding the remaining list of tablecell-contents.
\documentclass[a4paper]{article}
%\usepackage{hyperref}
\makeatletter
%%=============================================================================
%% Expandable handling of lists of non-delimited arguments:
%%-----------------------------------------------------------------------------
%% Pass first argument to second argument; Exchange two arguments:
%%.............................................................................
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% %% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
%% I explained this macro in my answer to the question
%% "Expandable test for an empty token list—methods, performance, and robustness"
%% at TeX-LaTeX-StackExchange: <https://tex.stackexchange.com/a/522506/118714>
%%
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
{\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%% With e-TeX-extensions' \detokenize available you can do:
%\newcommand\UD@CheckWhetherNull[1]{%
% \romannumeral0\if\relax\detokenize{#1}\relax
% \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
% {\@firstoftwo\expandafter{} \@firstoftwo}%
% {\@firstoftwo\expandafter{} \@secondoftwo}%
%}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \romannumeral0\UD@ExtractFirstArgLoop{<list of non-delimited arguments>\UD@SelDOm}
%%
%% yields the first non-delimited argument of the <list of non-delimited arguments>
%%
%% \romannumeral0\UD@ExtractFirstArgLoop{ABCDE\UD@SelDOm} yields A
%% \romannumeral0\UD@ExtractFirstArgLoop{{AB}CDE\UD@SelDOm} yields AB
%%
%% <list of non-delimited arguments> may contain the token \UD@SelDOm.
%% <list of non-delimited arguments> must not be empty.
%% Emptiness/non-emptiness of <list of non-delimited arguments> can be
%% checked via \UD@CheckWhetherNull.
%%-----------------------------------------------------------------------------
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
{\@firstoftwo\expandafter{} \@secondoftwo{}#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
\newcounter{ap}
% The starred variant of \apref is always without hyperlink.
% The non-starred variant of \apref is without hyperlink only
% in case hyperref is not loaded.
\@ifdefinable\apref{%
\DeclareRobustCommand{\apref}{\@ifstar\apref@star\apref@nostar}%
}%
\@ifpackageloaded{hyperref}{%
\newcommand{\apref@nostar}[1]{\hyperref[{ap:#1}]{A\ref*{ap:#1}}}%
\newcommand{\apref@star}[1]{A\ref*{ap:#1}}%
}{%
\newcommand{\apref@nostar}[1]{A\ref{ap:#1}}%
\newcommand{\apref@star}[1]{A\ref{ap:#1}}%
}%
% \ap is not used anywhere, therefore it is commented out:
%\newcommand{\ap}[1]{\refstepcounter{ap}\label{ap:#1}\apref{#1}}
\newcommand\FormTableRowFromNonDelimitedArgList{%
\romannumeral0\FormTableRowFromNonDelimitedArgListLoop{\hline}{\\}{&}{}%
}%
\newcommand\FormTableRowFromNonDelimitedArgListLoop[5]{%
% #1 = tokens to prepend to each table-row (\hline)
% #2 = tokens to append to each table-row (\\)
% #3 = tokens that separate contents of single cells (&)
% #4 = tokens of the table-row accumulated so far
% #5 = remaining list of tablecell-contents
\UD@CheckWhetherNull{#5}{%
\UD@CheckWhetherNull{#4}{ }{ #1#4#2}% <- the trailing spaces in \UD@CheckWhetherNull's
% 2nd/3rd argument terminate \romannumeral0-expansion
% started by \FormTableRowFromNonDelimitedArgList
}{%
\expandafter\UD@PassFirstToSecond\expandafter{\@gobble#5}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral0%
\expandafter\UD@Exchange\expandafter{%
\romannumeral0\UD@ExtractFirstArgLoop{#5\UD@SelDOm}%
}{%
\UD@CheckWhetherNull{#4}{ }{ #4#3}%
}%
}{%
\FormTableRowFromNonDelimitedArgListLoop{#1}{#2}{#3}%
}%
}%
}%
}%
% What happens when an action point gets defined:
\newcommand\actionpoint[4]{%
\ifvmode\leavevmode\fi
\refstepcounter{ap}%
\apref*{#1}%
\label{ap:#1}%
\addtocontents{lap}{%
\string\FormTableRowFromNonDelimitedArgList{%
{\string\apref{#1}}{#2}{#3}{#4}%
}\string\protected@file@percent
}%
}
% The list of action points.
\newcommand\tableofaps{%
\begingroup
% Patch \@input to wrap the call to \@@input (which is the LaTeX-name
% of the TeX-primitive \input) into \begin{tabular}..\end{tabular}%
\patch@input
\@starttoc{lap}%
\endgroup
}
\newcommand\patch@input{%
\let\saved@input=\@input
\def\@input##1{%
\let\@input=\saved@input
\IfFileExists{##1}{%
\begin{tabular}{|c|c|c|c|}%
\hline
heading1&heading2&heading3&heading4\\
\@@input\@filef@und
\hline
\end{tabular}%
}{\typeout{No file ##1.}}%
}%
}%
% Just to make sure it is not already defined...
\newcommand\saved@input{}%
\makeatother
\begin{document}
\tableofaps
\actionpoint{a1}{b1}{c1}{d1}
\actionpoint{a2}{b2}{c2}{d2}
\end{document}
Here the resulting .aux-file:
\relax
\newlabel{ap:a1}{{1}{1}}
\@writefile{lap}{\FormTableRowFromNonDelimitedArgList{{\apref{a1}}{b1}{c1}{d1}}\protected@file@percent}
\newlabel{ap:a2}{{2}{1}}
\@writefile{lap}{\FormTableRowFromNonDelimitedArgList{{\apref{a2}}{b2}{c2}{d2}}\protected@file@percent}
Here the resulting .lap-file:
\FormTableRowFromNonDelimitedArgList {{\apref {a1}}{b1}{c1}{d1}}%
\FormTableRowFromNonDelimitedArgList {{\apref {a2}}{b2}{c2}{d2}}%
Here a picture of the resulting .pdf-output-file:

\tableofapswrite the beginning and the end of the tabular-environment via\newaddtocontents/\addtocontents. This way the order in which\newwritefile-/\@writefile-entries appear in the .aux-file and thus in which lines appear in the .lap-file may be wrong.\tableofapsnow locally patches\@inputto wrap the call to\@@input(= the LaTeX 2e-name of the\input-primitive) into\begin{tabular}...\end{tabular}.This way\tableofapsshould also work when called in the middle or at the end of the document. – Ulrich Diez Jan 17 '20 at 14:27\expandafter? In my answer to the question How can I know the number of expandafters when appending to a csname macro? I tried to explain\expandafterand also how to avoid it / how to reduce the amount of\expandafterneeded. ;-) – Ulrich Diez Jan 17 '20 at 15:25